我该如何结合镜头和函子?

10

我正在尝试熟悉 Haskell 的 lens 库,但是在某些简单问题上遇到了困难。例如,为了方便起见,假设 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个回答

8
你想要一款像这样的镜头
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

这将允许您在地图中获取(使用previewtoListOf)和设置值在地图中的值的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)

因此,通常最好避免使用这些伪造的透镜以防止意外事件发生。


2
谢谢,我现在明白了我所要求的类型永远不可能是镜头! :) - wen
1
最后要注意的是,如果我们有一个 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)) - J. Abrahamson
只是一个小澄清:你的第一个例子不能成为一个镜头的原因是,当你有Nothing并尝试放入Just a时,你无法发明一个b来放入。反向情况可以很好地工作。这是我的尝试: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,但那不能满足镜头定律。 - Hjulle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接