Suppose I wanted to create an "optic" for the contents of MaybeT m a
:
maybeTContents = _Wrapped .
something
. _Just
Is there such a something
?
maybeTContents
would for example be a Traversal
when m
is []
,
but only a Setter
when m
is (->) Int
.
Example usage:
> MaybeT [Just 1, Nothing, Just 2] ^.. maybeTContents
[1, 2]
> runMaybeT (MaybeT (Just . ('e':)) & maybeTContents %~ ('H':)) "llo"
Just "Hello"
Yes! The first thing to note is that something
must have type Setter
(and, without loss of generality, Setter'
). As for what type let's use holes.
maybeTContents :: Setter' (MaybeT m a) a
maybeTContents =
_Wrapped . _ . _Just
GHC tells us it wants type Settable f => (Maybe a -> f (Maybe a)) -> (m (Maybe a) -> f (m (Maybe a))
for the hole.
With a trip to Hackage we recognize this type as Setter' (m (Maybe a)) (Maybe a)
. So, fixing u ~ Maybe a
, we can rephrase the question more generally: does a setter exist that unifies with both Setter' [u] u
exist and Setter' (Reader u) u
?
But, as both []
and Reader
have functor instances we can turn to an absolute classic of a setter mapped
, the setter heard around the world. mapped
has type mapped :: Functor f => Setter (f a) (f b) a b
– it turns out when you have a functor instance available that mapped = sets fmap
is the value that obeys all the setter laws.
We can see this in action here:
% stack ghci
GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help
Ok, modules loaded: none.
λ> import Control.Lens
λ> import Control.Monad.Trans.Maybe
λ> import Control.Monad.Trans.Reader
λ> MaybeT [Just 1, Nothing, Just 2, Nothing, Just 3] & _Wrapped . mapped . _Just .~ 100
MaybeT [Just 100,Nothing,Just 100,Nothing,Just 100]
λ> data A = A
λ> data B = B
λ> :t MaybeT (ReaderT (\r -> Identity (Just A)))
MaybeT (ReaderT (\r -> Identity (Just A)))
:: MaybeT (ReaderT r Identity) A
λ> :t MaybeT (ReaderT (\r -> Identity (Just A))) & _Wrapped . mapped . _Just .~ B
MaybeT (ReaderT (\r -> Identity (Just A))) & _Wrapped . mapped . _Just .~ B
:: MaybeT (ReaderT r Identity) B
As there was no Show
instance for ReaderT
the best I could do to illustrate that the setter worked was to generate two brand-spankin'-new types A
and B
.
This question is great I think because it gets at the heart of the motivation behind the lens
package. Given fmapDefault
from the Traversable
world, you can fix the traversable to be Identity
to write over
. You can then write the inverse of over
, sets
, such that over . sets = id
and sets . over = id
. We are then forced to conclude that mapped = sets fmap
is a natural setter that obeys the kind of laws we want for setters, one of the most important being that mapped . mapped . mapped
composes with (.)
. The rest of lens
soon follows.
One way to do this is to make your own class that gives the right optic for the type your using:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
class Ocular f t where
optic :: LensLike f (t a) (t b) a b
instance Settable f => Ocular f ((->) a) where
optic = mapped
instance Functor f => Ocular f Identity where
optic = _Wrapped
instance Applicative f => Ocular f [] where
optic = traversed
instance Applicative f => Ocular f Maybe where
optic = _Just
This will give a setter for (->) s
and a traversal for []
etc.
> let maybeTContents = _Wrapped . optic . _Just
> MaybeT [Just 1, Nothing, Just 2] ^.. maybeTContents
[1,2]
> runMaybeT (MaybeT (Just . ('e':)) & maybeTContents %~ ('H':)) "llo"
Just "Hello"
You can also write an instance for MaybeT
and ReaderT
:
instance (Applicative f, Ocular f m) => Ocular f (MaybeT m) where
optic = _Wrapped . optic . _Just
instance (Ocular f m, Settable f) => Ocular f (ReaderT r m) where
optic = _Wrapped . mapped . optic
> MaybeT [Just 1, Nothing, Just 2] ^.. optic
[1,2]
> runReaderT (ReaderT (\r -> [r,r+1]) & optic *~ 2) 1
[2,4]
Note that the Identity
case is only a Lens
, not an Iso
. For that you need to include the Profuctor
in the Ocular
class. You can also write a version that allows indexed lens and traversals this way.
Few examples based on previous answers:
> MaybeT [Just 1, Nothing, Just 2] ^.. _Wrapped . traverse . _Just
[1,2]
> runMaybeT (MaybeT (Just . ('e':)) & _Wrapped . collect . _Just %~ ('H':)) "llo"
Just "Hello"
I.e. for Traversal
/Fold
we use traverse
, for Setter
: collect
(or mapped
).
Unfortunately Traversable
and Distributive
don't have all instances:
(->) r
is not Traversable
and Const
is not Distributive
(and they can't be, AFAICS).
If you think about this, you see that it makes sense. Traversal
and Distributive
are duals, se to "go in other direction" of traverse
we use collect
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With