Haskell 用'at'和'ix'组合镜头`
假设我有一个相当简单的数据类型Haskell 用'at'和'ix'组合镜头`,haskell,traversal,haskell-lens,Haskell,Traversal,Haskell Lens,假设我有一个相当简单的数据类型Person,有几个字段,还有一个类型包含Person的集合 data Person = Person { _name :: String, _age :: Int } data ProgramState = PS { _dict :: IntMap Person } makeLenses ''Person makeLenses ''ProgramState 我想创建一个镜头,允许我通过查找个人的钥匙来访问他们 person :: Int -> Len
Person
,有几个字段,还有一个类型包含Person
的集合
data Person = Person { _name :: String, _age :: Int }
data ProgramState = PS { _dict :: IntMap Person }
makeLenses ''Person
makeLenses ''ProgramState
我想创建一个镜头,允许我通过查找个人的钥匙来访问他们
person :: Int -> Lens' ProgramState Person
我的两个选择似乎是使用at
或ix
索引到字典中
-- Option 1, using 'at'
person :: Int -> Lens' ProgramState (Maybe Person)
person key = dict . at key
-- Option 2, using 'ix'
person :: Int -> Traversal' ProgramState Person
person key = dict . ix key
但这两个选项都不允许我做我想做的事情,那就是拥有一个镜头'
,可以访问人
,而不是可能的人
。选项1不能很好地与其他镜头组合,选项2意味着我必须放弃我的getters
我理解为什么ix
和at
是这样写的。dict中可能不存在该键,因此如果您想要一个同时启用getter和setter的Lens'
,它必须访问或。另一种方法是接受一个遍历'
,该遍历允许访问0或1个值,但这意味着放弃getter。但是在我的例子中,我知道我想要的元素总是存在的,所以我不需要担心丢失键
有没有办法写我想写的东西?或者我应该重新思考我的程序的结构吗?你可能想把at
和非同构一起使用。您可以使用它指定一个默认映射条目,以摆脱查找的可能
non :: Eq a => a -> Iso' (Maybe a) a
person key = dict . at key . non defaultEntry
-- can get and set just like plain lenses
someProgramState & dict . at someKey . non defaultEntry .~ somePerson
你可以看更多的例子基于我最终定义了一个unsafeFromJust
镜头,它见证了我合成这些镜头所需要的“同构”
import Data.Maybe (fromJust)
unsafeFromJust :: Lens' (Maybe a) a
unsafeFromJust = lens fromJust setJust
where
setJust (Just _) b = Just a
setJust Nothing _ = error "setJust: Nothing"
另一种定义是
unsafeFromJust :: Lens' (Maybe a) a
unsafeFromJust = anon (error "unsafeFromJust: Nothing") (\_ -> False)
但我觉得这并不像第一张表格那么清楚。我没有使用非
,因为这需要一个Eq
实例,在本例中这是不必要的
我现在会写字了
person :: Lens' ProgramState Person
person key = dict . at key . unsafeFromJust
事实上,@Chris Taylor的回答是不正确的。您可以通过以下命令(在GHCi中)看到这一点:
第一个命令在查找不存在的值时仍会抛出错误,但不会抛出正确的错误。对于第二个测试,我无法插入新密钥,这似乎没有意义
相反,我使用以下两个组合符:
at' :: (Ord a) => a -> Lens' (Map a b) b
at' a = lens r s
where r m = case lookup a m of
(Just b) -> b
Nothing -> error "Could not find key in map!"
s m b' = insert a b' m
at'' :: (Ord a) => a -> Lens (Map a b) (Map a b) b (Maybe b)
at'' a = lens r s
where r m = case lookup a m of
(Just b) -> b
Nothing -> error "Could not find key in map!"
s m Nothing = delete a m
s m (Just b') = insert a b' m
at'
是(at k.unsafeFromJust)
应该如何工作的:尝试检索不存在的值会抛出错误,插入新值会成功<“
处的code>类似:它允许您读取纯值,但您可以设置值。这允许您删除地图中的关键点
示例:
> view (at' 0) (fromList [(0,'b')])
'b'
> view (at'' 0) (fromList [(0,'b')])
'b'
> view (at' 1) (fromList [(0,'b')])
*** Exception: Could not find key in map!
> view (at'' 1) (fromList [(0,'b')])
*** Exception: Could not find key in map!
> set (at' 0) 'c' (fromList [(0,'b')])
fromList [(0,'c')]
> set (at'' 0) (Just 'c') (fromList [(0,'b')])
fromList [(0,'c')]
> set (at' 1) 'c' (fromList [(0,'b')])
fromList [(0,'b'),(1,'c')]
> set (at'' 1) (Just 'c') (fromList [(0,'b')])
fromList [(0,'b'),(1,'c')]
> set (at'' 0) Nothing (fromList [(0,'b')])
fromList []
美好的在你的where子句中应该是setJust(Just)b=Just b
。你没有解决的关键问题是你是否确定你想要操纵的钥匙在地图上。除非钥匙总是在地图上,否则整个事情看起来都很可疑。在这种情况下,将其添加到地图似乎没有多大意义。探索“profunctor透镜”的方法可能会很有趣,在这种方法中,用棱镜构成透镜会产生仿射遍历。@d您是否愿意在回答中对此进行更多的解释?
> view (at' 0) (fromList [(0,'b')])
'b'
> view (at'' 0) (fromList [(0,'b')])
'b'
> view (at' 1) (fromList [(0,'b')])
*** Exception: Could not find key in map!
> view (at'' 1) (fromList [(0,'b')])
*** Exception: Could not find key in map!
> set (at' 0) 'c' (fromList [(0,'b')])
fromList [(0,'c')]
> set (at'' 0) (Just 'c') (fromList [(0,'b')])
fromList [(0,'c')]
> set (at' 1) 'c' (fromList [(0,'b')])
fromList [(0,'b'),(1,'c')]
> set (at'' 1) (Just 'c') (fromList [(0,'b')])
fromList [(0,'b'),(1,'c')]
> set (at'' 0) Nothing (fromList [(0,'b')])
fromList []