Search code examples
haskellhaskell-lens

How can I make (Fold s a) from Control.Lens a monoid?


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?


Solution

  • 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