10

I'm trying to get used to the lens library for Haskell, and find myself struggling at some simple problems. For instance, let's say (for convenience) that at and _1 have the following types (this is how I understand them, at least):

at :: Ord k => k -> Lens' (Map k v) (Maybe v)

_1 :: Lens' (a, b) a

How do I combine these lenses into a lens with the following type:

maybeFst :: Ord k => k -> Lens' (Map k (a, b)) (Maybe a)
wen
  • 3,782
  • 9
  • 34
  • 54

1 Answers1

8

You'd like a lens like

Lens' (Maybe (a, b)) (Maybe a)

but that can't quite be a Lens since putting back Nothing affects the b as well. It can be a Getter

getA :: Getter (Maybe (a, b)) (Maybe a)
getA = to (fmap fst)

but then when you compose it you'll just wind up with a Getter as well, not a full Lens

maybeFst :: Ord k => k -> Getter (Map k (a, b)) (Maybe a)
maybeFst k = at k . getA

Probably better than that is to use a Traversal instead

maybeFstT :: Ord k => k -> Traversal' (Map k (a, b)) a
maybeFstT k = at k . _Just . _1

This will allow you to both get (using preview or toListOf) and set values at the fst of the values in your map, but you won't be able to modify its existence in the map: if the value does not exist you cannot add it and if it does exist you cannot remove it.


Finally, we can jury-rig a fake Lens which has the appropriate type, though we have to give it a default value for b

getA :: b -> Lens' (Maybe (a, b)) (Maybe a)
getA b inj Nothing       = (\x -> (,b) <$> x) <$> inj Nothing
getA _ inj (Just (a, b)) = (\x -> (,b) <$> x) <$> inj (Just a)

but notice that it has some not-very-Lenslike behavior.

>>> Just (1, 2) & getA 0 .~ Nothing & preview (_Just . _2)
Nothing

>>> Nothing & getA 0 .~ Just 1
Just (1,0)

so often it's better to avoid these pseudolenses to prevent mishaps.

J. Abrahamson
  • 72,246
  • 9
  • 135
  • 180
  • 2
    Thank you, I see now how the type I was asking for could never be a lens! :) – wen Mar 12 '14 at 20:59
  • 1
    As a final note, if we had an `Iso' (a, b) a` (which obviously is impossible, then we could use `mapping ourIso :: Iso' (Maybe (a, b)) (Maybe a)` in place of `getA`. To wit, try `fl (a, b) = (b, a)` with `flipP = iso fl fl` and then `maybeFlip k = at k . mapping flipP :: Ord k => k -> Lens' (Map k (a, b)) (Maybe (b, a))`. – J. Abrahamson Mar 12 '14 at 21:04
  • Just a small clarification: The reason that your first example can't be a lens is that you can't invent a `b` to put in when having `Nothing` and trying to put in `Just a`. The inverse case works just fine. Here is my attempt: `impossible :: Lens' (Maybe (a,b)) (Maybe a);` `impossible k (Just (a,b)) = fmap (,b) <$> k (Just a);` `impossible k Nothing = fmap (,undefined) <$> k Nothing`. I first wrote `impossible k Nothing = Nothing <$ k Nothing`, but that doesn't fulfill the lens laws. – Hjulle Jan 18 '18 at 10:02