简体   繁体   中英

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 sa 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 fs 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 rsa = (a -> Const ra) -> s -> Const rs , 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM