19

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"
yairchu
  • 23,680
  • 7
  • 69
  • 109
  • @haoformayor regarding the title editing - `MaybeT` is just one example use of what I'm looking for which. hopefully the new title better explains what I'm looking for – yairchu Jun 22 '16 at 18:00
  • 2
    See also https://github.com/ekmett/lens/wiki/Varying-lens-properties-by-instance – phadej Jun 29 '16 at 17:29

3 Answers3

8

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.

hao
  • 10,138
  • 1
  • 35
  • 50
  • 1
    With `something = mapped`, could you do `MaybeT [Just 1, Nothing, Just 2] ^.. maybeTContents`? (Just updated my question to have this example) – yairchu Jun 22 '16 at 16:58
  • 1
    @yairchu Yes. All setters are also traversals. (See: that giant UML diagram on the lens Hackage page.) – hao Jun 22 '16 at 17:20
  • 1
    it doesn't seem to work. `:t MaybeT [Just 'a', Nothing, Just 'b'] ^.. _Wrapped . mapped . _Just` results in error `No instance for (Settable (Const (Data.Monoid.Endo [Char]))) arising from a use of ‘mapped’`... – yairchu Jun 22 '16 at 17:30
  • 1
    Oh. A doy. All traversals are setters, not the other way around. I always get that confused. You want `^.. _Wrapped . traverse . _Just` I think. No such traversal will exist for `Reader`. I don't think there's any way to traverse the `a` in a Reader. – hao Jun 22 '16 at 17:34
  • 1
    yeah I want something that will work with `^..` for `[]` but will also work with `%~` etc for `Reader`. I don't know if `Lens` already has something like it but it seems like it can be done - here's what I came up with so far - https://github.com/lamdu/lamdu/blob/master/bottlelib/Data/Traversable/Generalized.hs – yairchu Jun 22 '16 at 17:43
  • 1
    Hmm. `collect` typechecks but does it do what you want? It essentially depends on the `collect` from `Distributive Identity`, right? – hao Jun 22 '16 at 19:02
  • 1
    `collect` typechecks in the context of `_Wrapped . collect . _Just` but it doesn't seem to do what I want in all contexts.. – yairchu Jun 22 '16 at 20:11
  • I think that Reader intuitively won't admit a traversal here because you couldn't possibly know the value inside a Reader until you run it ... but it is difficult for me to formulate this with rigor. – hao Jun 22 '16 at 20:32
  • yeah this "optic" couldn't be a traversal in the case of `m` being `ReaderT r`, but it should be one when `m` is `[]`. – yairchu Jun 27 '16 at 12:56
6

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.

cchalmers
  • 2,896
  • 1
  • 11
  • 11
  • 1
    A better version of this is to use a class associated type for the constraint, w/ a member that proves your optic is at least some minimal level of optic. Hard to do in a 400 character comment where I can't format code, but `class HasFoo e where { type T e :: * -> Constraint; foo' :: T e f => LensLike' f e Foo; fooIsALens :: T e f :- Functor f }` Then you can define `foo :: HasFoo e => Lens' e Foo` by manipulating the constraint. In monomorphic cases you proceed as before w/ `foo'`, but now you can reasonably use `foo` parametrically w/out leaking every intermediate `f` into your signature. – Edward Kmett Jun 29 '16 at 20:34
  • @EdwardKMETT - Here's what I've got so far: https://github.com/lamdu/lamdu/blob/master/bottlelib/Data/Traversable/Generalized.hs almost like you described but with `Functor f` in the lens' constraints instead of `fooIsALens` – yairchu Jun 29 '16 at 21:20
  • 1
    @EdwardKMETT I had a go at it, is this what you mean? http://lpaste.net/168558 Can't you use type synonyms to get a similar effect (i.e. `SetFoo = HasFoo Identity`)? – cchalmers Jun 29 '16 at 23:57
  • You can use a Traversal at many different choices of f. If you use the version where that f leaks into the surrounding type signature and call, oh, `confusing something`, where the combinator `something` would pick `f = Something`, then all of a sudden you need `HasFoo (Curried (Yoneda Something) (Yoneda Something))` in the environment. And when you pick only one `f` you have to pay full price for it, even if you only needed something simpler. e.g. We don't always want to pay for a layer of `cloneLens` to go through Store, even though it doesn't change the answer for a lens. – Edward Kmett Jul 13 '16 at 15:04
1

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.

phadej
  • 11,947
  • 41
  • 78