Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I combine lenses and functors?

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)
like image 953
wen Avatar asked Mar 12 '14 20:03

wen


1 Answers

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.

like image 92
J. Abrahamson Avatar answered Oct 19 '22 06:10

J. Abrahamson