product-profunctors-0.8.0.3: product-profunctors

Safe HaskellNone
LanguageHaskell2010

Data.Profunctor.Product.TH

Description

If you have a data declaration which is a polymorphic product, for example

data Foo a b c = Foo a b c

or

data Foo a b c = Foo { foo :: a, bar :: b, baz :: c }

then you can use Template Haskell to automatically derive the product-profunctor Default instances and product-profunctor "adaptor" with the following splice:

$(makeAdaptorAndInstance "pFoo" ''Foo)

The adaptor for a type Foo is by convention called pFoo, but in practice you can call it anything. If you don't care to specify the name pFoo yourself you can use

$(makeAdaptorAndInstance' ''Foo)

and it will be named pFoo automatically.

pFoo will have the type

pFoo :: ProductProfunctor p
     => Foo (p a a') (p b b') (p c c')
     -> p (Foo a b c) (Foo a' b' c')

and the instance generated will be

instance (ProductProfunctor p, Default p a a', Default p b b', Default p c c')
      => Default p (Foo a b c) (Foo a' b' c')

If you are confused about the meaning of pFoo it may help to consider the corresponding function that works with Applicatives (its implementation is given below).

pFooApplicative :: Applicative f
                => Foo (f a) (f b) (f c)
                -> f (Foo a b c)

The product-profunctor "adaptor" (in this case pFoo) is a generalization of Data.Traversable.sequence in two different ways. Firstly it works on datatypes with multiple type parameters. Secondly it works on ProductProfunctors, which are themselves a generalization of Applicatives.

If your type has only one field, for example

data Foo a = Foo a

or

newtype Foo a = Foo a

then you will also get the instance

instance Newtype Foo where
  constructor = Foo
  field       = \(Foo x) -> x

which allows you to use the polymorphic function pNewtype instead of pFoo.

If you prefer not to use Template Haskell then the generated code can be written by hand because it is quite simple. It corresponds very closely to what we would do in the more familiar Applicative case. For an Applicative we would write

pFooApplicative :: Applicative f
                => Foo (f a) (f b) (f c) -> f (Foo a b c)
pFooApplicative f = Foo <$> foo f
                        <*> bar f
                        <*> baz f

whereas for a ProductProfunctor we write

import Data.Profunctor (lmap)
import Data.Profunctor.Product ((***$), (****))

pFoo :: ProductProfunctor p
     => Foo (p a a') (p b b') (p c c') -> p (Foo a b c) (Foo a' b' c')
pFoo f = Foo ***$ lmap foo (foo f)
             **** lmap bar (bar f)
             **** lmap baz (baz f)

The Default instance is then very simple.

instance (ProductProfunctor p, Default p a a', Default p b b', Default p c c')
      => Default p (Foo a b c) (Foo a' b' c') where
    def = pFoo (Foo def def def)

Synopsis

Documentation

makeAdaptorAndInstance :: String -> Name -> Q [Dec] Source #

For example

$(makeAdaptorAndInstance "pFoo" ''Foo)

generates the Default instance and the adaptor pFoo.

makeAdaptorAndInstance' :: Name -> Q [Dec] Source #

For example

$(makeAdaptorAndInstance ''Foo)

generates the Default instance and the adaptor pFoo. The name of the adaptor is chosen by prefixing the type name "Foo" with the string "p".