-----------------------------------------------------------------------------
-- |
-- Module      :  Generics.EMGM.Base
-- Copyright   :  (c) 2008, 2009 Universiteit Utrecht
-- License     :  BSD3
--
-- Maintainer  :  generics@haskell.org
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Summary: Type classes used for generic functions with /one/ generic argument.
--
-- A /generic function/ is defined as an instance of 'Generic', 'Generic2', or
-- 'Generic3'. Each method in the class serves for a case in the datatype
-- representation
--
-- A /representation dispatcher/ simplifies the use of a generic function. There
-- must be an instance of each of the classes 'Rep', 'FRep', 'FRep2', etc. (that
-- apply) for every datatype.
-----------------------------------------------------------------------------

{-# OPTIONS_GHC -Wall #-}

{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators         #-}
{-# LANGUAGE OverlappingInstances  #-}

module Generics.EMGM.Base (

  -- | Re-export the types and related functions for the structure
  -- representation.

  module Generics.EMGM.Representation,

  -- * Classes for Generic Functions

  Generic(..),
  Generic2(..),
  Generic3(..),

  -- * Classes for Representation Dispatchers

  Rep(..),
  FRep(..),
  FRep2(..),
  BiFRep2(..),
  FRep3(..),

) where

import Generics.EMGM.Representation

-- | This class forms the foundation for defining generic functions with a
-- single generic argument. Each method represents a type case. There are cases
-- for primitive types, structural representation types, and for user-defined
-- datatypes.
--
-- The included modules using @Generic@ are:
--
-- * "Generics.EMGM.Functions.Collect"
--
-- * "Generics.EMGM.Functions.Compare"
--
-- * "Generics.EMGM.Functions.Constructor"
--
-- * "Generics.EMGM.Functions.Crush"
--
-- * "Generics.EMGM.Functions.Enum"
--
-- * "Generics.EMGM.Functions.Read"
--
-- * "Generics.EMGM.Functions.Show"

class Generic g where

  -- | Case for the primitive type 'Int'.
  rint      :: g Int

  -- | Case for the primitive type 'Integer'.

  rinteger  :: g Integer

  -- | Case for the primitive type 'Float'.

  rfloat    :: g Float

  -- | Case for the primitive type 'Double'.

  rdouble   :: g Double

  -- | Case for the primitive type 'Char'.

  rchar     :: g Char

  -- | Case for the structural representation type 'Unit'. Represents a
  -- constructor with no arguments.

  runit     :: g Unit

  -- | Case for the structural representation type @:+:@ (sum). Represents
  -- alternative constructors.

  rsum      :: g a -> g b -> g (a :+: b)

  -- | Case for the structural representation type @:*:@ (product). Represents
  -- the fields of a constructor.

  rprod     :: g a -> g b -> g (a :*: b)

  -- | Case for constructors. It is used to hold the meta-information about a
  -- constructor, e.g. name, arity, fixity, etc. This is not needed for many
  -- generic functions, so the default implementation is:
  --
  -- @
  --   rcon = const id
  -- @

  rcon      :: ConDescr -> g a -> g a
  rcon      = const id

  -- | Case for labeled field. Contains the label string. This is not needed for
  -- many generic functions, so the default implementation is:
  --
  -- @
  --   rlbl = const id
  -- @

  rlbl      :: LblDescr -> g a -> g a
  rlbl      = const id

  -- | Case for datatypes. This method is used to define the structural
  -- representation of an arbitrary Haskell datatype. The first argument is the
  -- embedding-projection pair, necessary for establishing the isomorphism
  -- between datatype and representation. The second argument is the
  -- run-time representation using the methods of 'Generic'.

  rtype     :: EP b a -> g a -> g b


infixr 5 `rsum`
infixr 6 `rprod`

-- | This class forms the foundation for defining generic functions with two
-- generic arguments. See 'Generic' for details.
--
-- The included modules using @Generic2@ are:
--
-- * "Generics.EMGM.Functions.Map"
--
-- * "Generics.EMGM.Functions.Transpose"

class Generic2 g where

  rint2      :: g Int Int
  rinteger2  :: g Integer Integer
  rfloat2    :: g Float Float
  rdouble2   :: g Double Double
  rchar2     :: g Char Char
  runit2     :: g Unit Unit
  rsum2      :: g a1 a2 -> g b1 b2 -> g (a1 :+: b1) (a2 :+: b2)
  rprod2     :: g a1 a2 -> g b1 b2 -> g (a1 :*: b1) (a2 :*: b2)

  rcon2      :: ConDescr -> g a1 a2 -> g a1 a2
  rcon2      = const id

  rlbl2      :: LblDescr -> g a1 a2 -> g a1 a2
  rlbl2      = const id

  -- | See 'rtype'. This case is the primary difference that separates
  -- 'Generic2' from 'Generic'. Since we have two generic type parameters, we
  -- need to have two 'EP' values. Each translates between the Haskell type and
  -- its generic representation.
  rtype2     :: EP a2 a1 -> EP b2 b1 -> g a1 b1 -> g a2 b2


infixr 5 `rsum2`
infixr 6 `rprod2`

-- | This class forms the foundation for defining generic functions with three
-- generic arguments. See 'Generic' for details.
--
-- The included modules using @Generic3@ are:
--
-- * "Generics.EMGM.Functions.UnzipWith"
--
-- * "Generics.EMGM.Functions.ZipWith"

class Generic3 g where

  rint3      :: g Int Int Int
  rinteger3  :: g Integer Integer Integer
  rfloat3    :: g Float Float Float
  rdouble3   :: g Double Double Double
  rchar3     :: g Char Char Char
  runit3     :: g Unit Unit Unit
  rsum3      :: g a1 a2 a3 -> g b1 b2 b3 -> g (a1 :+: b1) (a2 :+: b2) (a3 :+: b3)
  rprod3     :: g a1 a2 a3 -> g b1 b2 b3 -> g (a1 :*: b1) (a2 :*: b2) (a3 :*: b3)

  rcon3      :: ConDescr -> g a1 a2 a3 -> g a1 a2 a3
  rcon3      = const id

  rlbl3      :: LblDescr -> g a1 a2 a3 -> g a1 a2 a3
  rlbl3      = const id

  -- | See 'rtype'. This case is the primary difference that separates
  -- 'Generic3' from 'Generic'. Since we have three generic type parameters, we
  -- need three 'EP' values. Each translates between the Haskell type and its
  -- generic representation.
  rtype3     :: EP a2 a1 -> EP b2 b1 -> EP c2 c1 -> g a1 b1 c1 -> g a2 b2 c2


infixr 5 `rsum3`
infixr 6 `rprod3`

-- | Representation dispatcher for monomorphic types (kind @*@) used with
-- 'Generic'. Every structure type and supported datatype should have an
-- instance of 'Rep'.

class Rep g a where
  rep :: g a

instance (Generic g) => Rep g Int where
  rep = rint

instance (Generic g) => Rep g Integer where
  rep = rinteger

instance (Generic g) => Rep g Float where
  rep = rfloat

instance (Generic g) => Rep g Double where
  rep = rdouble

instance (Generic g) => Rep g Char where
  rep = rchar

instance (Generic g) => Rep g Unit where
  rep = runit

instance (Generic g, Rep g a, Rep g b) => Rep g (a :+: b) where
  rep = rsum rep rep

instance (Generic g, Rep g a, Rep g b) => Rep g (a :*: b) where
  rep = rprod rep rep

-- | Representation dispatcher for functor types (kind @* -> *@) used with
-- 'Generic'.

class FRep g f where
  frep :: g a -> g (f a)

-- | Representation dispatcher for functor types (kind @* -> *@) used with
-- 'Generic2'.

class FRep2 g f where
  frep2 :: g a b -> g (f a) (f b)

-- | Representation dispatcher for bifunctor types (kind @* -> *@) used with
-- 'Generic2'.

class BiFRep2 g f where
  bifrep2 :: g a1 b1 -> g a2 b2 -> g (f a1 a2) (f b1 b2)

-- | Representation dispatcher for functor types (kind @* -> *@) used with
-- 'Generic3'.

class FRep3 g f where
  frep3 :: g a b c -> g (f a) (f b) (f c)