Safe Haskell | None |
---|---|
Language | Haskell2010 |
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 Applicative
s
(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 ProductProfunctor
s, which are themselves a
generalization of Applicative
s.
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
instanceNewtype
Foo whereconstructor
= Foofield
= \(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
- makeAdaptorAndInstance :: String -> Name -> Q [Dec]
- makeAdaptorAndInstance' :: Name -> Q [Dec]