{-# LANGUAGE
    TypeFamilies
  , ConstraintKinds
  , MultiParamTypeClasses
  , FlexibleInstances
  , UndecidableInstances
  #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Algebra.Internal
-- Copyright   :  (c) Sjoerd Visscher 2013
-- License     :  BSD-style (see the file LICENSE)
--
-- Maintainer  :  sjoerd@w3future.com
-- Stability   :  experimental
-- Portability :  non-portable
-----------------------------------------------------------------------------
module Data.Algebra.Internal where

import GHC.Exts (Constraint)
import Control.Applicative (Const(..))

import GHC.Conc (STM)
import Control.Arrow ((&&&))

-- | The signature datatype for the class @c@.
type family Signature (c :: * -> Constraint) :: * -> *

class Traversable f => AlgebraSignature f where
  -- | The class for which @f@ is the signature.
  type Class f :: * -> Constraint
  -- | Translate the operations of the signature to method calls of the class.
  evaluate :: Class f b => f b -> b

class Algebra f a where
  -- | An algebra @f a -> a@ corresponds to an instance of @a@ of the class @Class f@.
  --   In some cases, for example for tuple types, you can give an algebra generically for every signature:
  --
  -- > instance (Class f m, Class f n) => Algebra f (m, n) where
  -- >   algebra fmn = (evaluate (fmap fst fmn), evaluate (fmap snd fmn))
  algebra :: AlgebraSignature f => f a -> a

-- | If you just want to applicatively lift existing instances, you can use this default implementation of `algebra`.
algebraA :: (Applicative g, Class f b, AlgebraSignature f) => f (g b) -> g b
algebraA = fmap evaluate . sequenceA

instance Algebra f () where
  algebra = const ()
instance (Class f m, Class f n) => Algebra f (m, n) where
  algebra = evaluate . fmap fst &&& evaluate . fmap snd

instance Class f b => Algebra f (a -> b) where algebra = algebraA
instance Class f b => Algebra f (IO b) where algebra = algebraA
instance Class f b => Algebra f (Maybe b) where algebra = algebraA
instance Class f b => Algebra f (Either a b) where algebra = algebraA
instance Class f b => Algebra f (STM b) where algebra = algebraA
instance (Monoid m, Class f b) => Algebra f (Const m b) where algebra = algebraA