6

假设我有一些相当简单的数据类型Person,其中包含几个字段,以及一个包含Persons 集合的类型。

data Person = Person { _name :: String, _age  :: Int }

data ProgramState = PS { _dict :: IntMap Person }

makeLenses ''Person
makeLenses ''ProgramState

我想创建一个镜头,让我可以通过查找他们的密钥来访问个人

person :: Int -> Lens' ProgramState Person

看来我这样做的两个选择是使用atix索引到字典中

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

但是这些选项都不能让我做我想做的事,那就是让 aLens'访问 aPerson而不是 a Maybe Person。选项 1 不能与其他镜头很好地组合,选项 2 意味着我必须放弃我的吸气剂。

我理解为什么 ix并且at是这样写的。字典中可能不存在密钥,因此如果您想要一个Lens'同时启用 getter 和 setter 的 a,它必须访问 a Maybe a。另一种方法是接受 a Traversal',它可以访问 0 或 1 值,但这意味着放弃你的吸气剂。但就我而言,我知道我想要的元素将始终存在,所以我不需要担心丢失键。

有没有办法写出我想写的东西——或者我应该重新考虑我的程序结构?

4

3 回答 3

5

您可能希望与同构at一起使用。non您可以使用它指定默认映射条目以摆脱Maybe查找。

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

您可以在文档中查看更多示例。

于 2014-01-03T11:00:46.817 回答
3

根据András Kovács 的回答,我最终定义了一个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)

但我觉得这不像第一种形式那么清楚。我没有使用non,因为这需要Eq在这种情况下不必要的实例。

我现在可以写了

person :: Lens' ProgramState Person
person key = dict . at key . unsafeFromJust
于 2014-01-03T13:20:01.477 回答
0

实际上,@Chris Taylor 的回答是不正确的。您可以使用以下命令(在 GHCi 中)看到这一点:

>view (at 0 . unsafeFromJust) (fromList [(0,'b')])
*** Exception: Maybe.fromJust: Nothing
[expected: *** Exception: setJust]

>set (at 1 . unsafeFromJust) 'c' (fromList [(0,'b')])
*** Exception: setJust]
[expected: fromList [(0,'b'),(1,'c')]

在查找不存在的值时,第一个命令仍然会引发错误,但不会引发正确的错误。对于第二个测试,我无法插入新密钥,这似乎没有意义。

相反,我使用以下两个组合器:

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)工作:尝试检索不存在的值会引发错误,插入新值成功。at''类似:它允许您读取纯值,但您设置 Maybe值。这允许您删除地图中的键。

例子:

> 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 []
于 2018-03-07T19:55:17.527 回答