Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic LensLike like mapped and traverse

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"
like image 566
yairchu Avatar asked Jun 22 '16 11:06

yairchu


3 Answers

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.

like image 154
hao Avatar answered Nov 10 '22 18:11

hao


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.

like image 21
cchalmers Avatar answered Nov 10 '22 19:11

cchalmers


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.

like image 45
phadej Avatar answered Nov 10 '22 18:11

phadej