{-|
Module:      Data.Deriving
Copyright:   (C) 2015-2016 Ryan Scott
License:     BSD-style (see the file LICENSE)
Maintainer:  Ryan Scott
Portability: Template Haskell

This module reexports all of the functionality of the other modules in this library.
It also provides a high-level tutorial on @deriving-compat@'s naming conventions and
best practices. Typeclass-specific information can be found in their respective
modules.
-}
module Data.Deriving (
      -- * @derive@- functions
      -- $derive

      -- * @make@- functions
      -- $make
      module Exports
    ) where

import Data.Foldable.Deriving    as Exports
import Data.Functor.Deriving     as Exports
import Data.Traversable.Deriving as Exports

{- $derive

Functions with the @derive@- prefix can be used to automatically generate an instance
of a typeclass for a given datatype 'Name'. Some examples:

@
{-# LANGUAGE TemplateHaskell #-}
import Data.Deriving

data Pair a = Pair a a
$('deriveFunctor' ''Pair) -- instance Functor Pair where ...

data Product f g a = Product (f a) (g a)
$('deriveFoldable' ''Product)
-- instance (Foldable f, Foldable g) => Foldable (Pair f g) where ...
@

If you are using @template-haskell-2.7.0.0@ or later (i.e., GHC 7.4 or later),
then @derive@-functions can be used with data family instances (which requires the
@-XTypeFamilies@ extension). To do so, pass the 'Name' of a data or newtype instance
constructor (NOT a data family name!) to @deriveFoldable@.  Note that the
generated code may require the @-XFlexibleInstances@ extension. Example:

@
{-# LANGUAGE FlexibleInstances, TemplateHaskell, TypeFamilies #-}
import Data.Deriving

class AssocClass a b where
    data AssocData a b
instance AssocClass Int b where
    data AssocData Int b = AssocDataInt1 Int
                         | AssocDataInt2 b
$('deriveFunctor' 'AssocDataInt1) -- instance Functor (AssocData Int) where ...
-- Alternatively, one could use $(deriveFunctor 'AssocDataInt2)
@

@derive@-functions in @deriving-compat@ fall into one of three categories:

* Category 0: Typeclasses with an argument of kind @*@.
  ('deriveEq', 'deriveOrd', 'deriveRead', 'deriveShow')

* Category 1: Typeclasses with an argument of kind @* -> *@, That is, a datatype
  with such an instance must have at least one type variable, and the last type
  variable must be of kind @*@.
  ('deriveEq1', 'deriveFoldable', 'deriveFunctor', 'deriveOrd1',
   'deriveRead1', 'deriveShow1', 'deriveTraversable')

* Category 2: Typeclasses with an argument of kind @* -> * -> *@. That is, a datatype
  with such an instance must have at least two type variables, and the last two type
  variables must be of kind @*@.
  ('deriveEq2', 'deriveOrd2', 'deriveRead2', 'deriveShow2')

Note that there are some limitations to @derive@-functions:

* The 'Name' argument must not be of a type synonym.

* Type variables (other than the last ones) are assumed to require typeclass
  constraints. The constraints are different depending on the category. For example,
  for Category 0 functions, other type variables of kind @*@ are assumed to be
  constrained by that typeclass. As an example:

  @
  data Foo a = Foo a
  $(deriveEq ''Foo)
  @

  will result in a generated instance of:

  @
  instance Eq a => Eq (Foo a) where ...
  @

  If you do not want this behavior, use a @make@- function instead.

* For Category 1 and 2 functions, if you are using the @-XDatatypeContexts@ extension,
  a constraint cannot mention the last type variables. For example,
  @data Illegal a where I :: Ord a => a -> Illegal a@ cannot have a derived 'Functor'
  instance.

* For Category 1 and 2 functions, if one of the last type variables is used within a
  constructor field's type, it must only be used in the last type arguments. For
  example, @data Legal a = Legal (Either Int a)@ can have a derived 'Functor' instance,
  but @data Illegal a = Illegal (Either a Int)@ cannot.

* For Category 1 and 2 functions, data family instances must be able to eta-reduce the
  last type variables. In other words, if you have a instance of the form:

  @
  data family Family a1 ... an t1 ... tn
  data instance Family e1 ... e2 v1 ... vn = ...
  @

  where @t1@, ..., @tn@ are the last type variables, then the following conditions
  must hold:

  1. @v1@, ..., @vn@ must be type variables.
  2. @v1@, ..., @vn@ must not be mentioned in any of @e1@, ..., @e2@.

-}

{- $make

Functions prefixed with @make@- are similar to @derive@-functions in that they also
generate code, but @make@-functions in particular generate the expression for a
particular typeclass method. For example:

@
{-# LANGUAGE TemplateHaskell #-}
import Data.Deriving

data Pair a = Pair a a

instance Functor Pair where
    fmap = $('makeFmap' ''Pair)
@

In this example, 'makeFmap' will splice in the appropriate lambda expression which
implements 'fmap' for @Pair@.

@make@-functions are subject to all the restrictions of @derive@-functions listed
above save for one exception: the datatype need not be an instance of a particular
typeclass. There are some scenarios where this might be preferred over using a
@derive@-function. For example, you might want to map over a @Pair@ value
without explicitly having to make it an instance of 'Functor'.

Another use case for @make@-functions is sophisticated data types—that is, an
expression for which a @derive@-function would infer the wrong instance context.
Consider the following example:

@
data Proxy a = Proxy
$('deriveEq' ''Proxy)
@

This would result in a generated instance of:

@
instance Eq a => Eq (Proxy a) where ...
@

This compiles, but is not what we want, since the @Eq a@ constraint is completely
unnecessary. Another scenario in which @derive@-functions fail is when you
have something like this:

@
newtype HigherKinded f a b = HigherKinded (f a b)
$('deriveFunctor' ''HigherKinded)
@

Ideally, this would produce @HigherKinded (f a)@ as its instance context, but sadly,
the Template Haskell type inference machinery used in @deriving-compat@ is not smart
enough to figure that out. Nevertheless, @make@-functions provide a valuable
backdoor for these sorts of scenarios:

@
{-# LANGUAGE FlexibleContexts, TemplateHaskell #-}
import Data.Foldable.Deriving

data Proxy a = Proxy
newtype HigherKinded f a b = HigherKinded (f a b)

instance Eq (Proxy a) where
    (==) = $('makeEq' ''Proxy)

instance Functor (f a) => Functor (HigherKinded f a) where
    fmap = $('makeFmap' ''HigherKinded)
@

-}