Как объединить линзы и функторы?

Я пытаюсь привыкнуть к библиотеке lens для Haskell и пытаюсь решить некоторые простые проблемы. Например, допустим (для удобства), что at и _1 имеют следующие типы (по крайней мере, я их понимаю):

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

_1 :: Lens' (a, b) a

Как объединить эти линзы в объектив со следующим типом:

maybeFst :: Ord k => k -> Lens' (Map k (a, b)) (Maybe a)

Ответ 1

Вам нужен объектив, например

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

но это не может быть Lens, так как возврат Nothing также влияет на b. Это может быть Getter

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

но затем, когда вы его создадите, вы просто закончите с Getter, а не полный Lens

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

Вероятно, лучше, чем использовать Traversal вместо

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

Это позволит вам получить (используя preview или toListOf) и установить значения в fst значений на вашей карте, но вы не сможете изменить его существование на карте: если значение не существует, вы не можете его добавить, и если он существует, вы не сможете его удалить.


Наконец, мы можем жюри-подставку подделать Lens, который имеет соответствующий тип, хотя мы должны дать ему значение по умолчанию для 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)

но обратите внимание, что он имеет некоторое не очень-тесное поведение.

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

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

так часто лучше избегать этих псевдоуровней, чтобы предотвратить неудачи.