Как совместить линзы и функторы?

Я пытаюсь привыкнуть к библиотеке 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)

person wen    schedule 12.03.2014    source источник


Ответы (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)

но обратите внимание, что у него есть не совсем Lensподобное поведение.

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

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

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

person J. Abrahamson    schedule 12.03.2014
comment
Спасибо, теперь я понимаю, что тип, о котором я просил, никогда не мог быть объективом! :) - person wen; 13.03.2014
comment
И последнее замечание: если бы у нас был Iso' (a, b) a (что, очевидно, невозможно, то мы могли бы использовать mapping ourIso :: Iso' (Maybe (a, b)) (Maybe a) вместо getA. А именно, попробуйте fl (a, b) = (b, a) с flipP = iso fl fl, а затем maybeFlip k = at k . mapping flipP :: Ord k => k -> Lens' (Map k (a, b)) (Maybe (b, a)). - person J. Abrahamson; 13.03.2014
comment
Небольшое уточнение: причина, по которой ваш первый пример не может быть линзой, заключается в том, что вы не можете изобрести b для вставки, имея Nothing и пытаясь вставить Just a. Обратный случай работает нормально. Вот моя попытка: impossible :: Lens' (Maybe (a,b)) (Maybe a); impossible k (Just (a,b)) = fmap (,b) <$> k (Just a); impossible k Nothing = fmap (,undefined) <$> k Nothing. Сначала я написал impossible k Nothing = Nothing <$ k Nothing, но это не соответствует законам объектива. - person Hjulle; 18.01.2018