How do I combine lenses and functors?
Asked Answered
G

1

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)
Glengarry answered 12/3, 2014 at 20:34 Comment(0)
P
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.

Plethoric answered 12/3, 2014 at 20:49 Comment(3)
Thank you, I see now how the type I was asking for could never be a lens! :)Glengarry
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)).Plethoric
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.Buote

© 2022 - 2024 — McMap. All rights reserved.