I'm fooling around with lenses and prisms and I've gotten into the weeds a bit. I want to write the following in template Haskell but as is it doesn't compile:
data Suit = Spade | Heart | Diamond | Club
makePrisms ''Suit
blackSuits = _Spade <> _Club
Everything I know about lenses and prisms (as well as comments in the Getter
documentation) suggests that _Spade <> _Club
should be a valid Fold Suit ()
. But ghc can't compile the above code; it complains about several ambiguous instances. ghci gives me the following type signature for _Spade <> _Club
:
_Spade <> _Club
:: (Applicative f, Monoid (p Suit (f Suit)), Choice p) =>
p () (f ()) -> p Suit (f Suit)
And by replacing p
with (->)
I can relax that type to
(Applicative f, Monoid (f Suit)) => (() -> f ()) -> Suit -> f Suit
The only difference between that and Fold Suit ()
is that the latter has (Applicative f, Contravariant f) => ...
instead of the Monoid (f Suit)
restriction. When I read up on Contravariant
I see assertions that (Applicative f, Contravariant f)
together imply that f
is actually Const r
for some monoid r
. That seems to suggest that the type above is actually a subset of Fold Suit ()
. But when I try to add blackSuits :: Fold Suit ()
to my code I get
Could not deduce (Monoid (f Suit)) arising from a use of ‘<>’
from the context (Contravariant f, Applicative f) ...
I get similar errors if I just try to define monoids on folds instead of starting with prisms. Nothing I've done to try and make Fold s a
a Monoid
passes the compiler. Is there any way to make this work?
If you look at the Fold
signature:
type Fold s a = forall f. (Contravariant f, Applicative f)=> (a -> f a) -> s -> f s
it says it has to work for all f
that are Contravariant
and Applicative
. When you combine two folds together using <>
, the Semigroup
instance for functions instance Semigroup b => Semigroup (a -> b)
requires that f s
is Semigroup
. That means it will no longer type check as a Fold
. However, most (all?) functions that take a fold in lens
will still accept this because they take a Getting r s a = (a -> Const r a) -> s -> Const r s
, which this will type check for.
There's a couple of things you can do. You could use the Fold
newtype which has its own Monoid
instance:
> :set -XTemplateHaskell
> import Control.Lens
> import Data.Semigroup
> data Suit = Spade | Heart | Diamond | Club; makePrisms ''Suit
> let blackSuitsFold = runFold $ Fold _Spade <> Fold _Club :: Fold Suit ()
Or you could "clone" the fold:
> let cloneFold = foldring . foldrOf
> let blackSuitsClone = cloneFold $ _Spade <> _Club :: Fold Suit ()
or, if you don't need it to be a Fold
you can use Getting
synonym which would work the same in most cases:
> let blackSuitsGetter = _Spade `mappend` _Club :: Monoid r => Getting r Suit ()
> has blackSuitsGetter Spade
True