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 []