Haskell 使用一级镜头制作复杂镜头
Haskell use first level lenses to create complex lens
比方说,我有一个包含两个字段的对象:
data Example = Example { _position :: Int
, _storage :: [Int]}
我如何构建聚焦于 storage
内的 position
元素的镜头?
此外,是否可以将通过镜头修改的 position
值限制在基于 storage
大小的范围内?
似乎 alongside 可以以某种方式使用,因为 Example
与元组同构,但我无法理解这样做的方法。
我不确定如何表述这个问题,所以找不到太多相关信息。
我认为棱镜应该更适合这种情况,因为 pos
可能是一个不大于列表长度的整数,或者是负数。
我想你可以使用类似 docu for prisms provide
的东西
nat :: Prism' Integer Natural
nat = prism toInteger $ \ i ->
if i < 0
then Left i
else Right (fromInteger i)
storageAtPos Prism' Example Int
storageAtPos = prism $ aux
where aux (Example p s) | p < 0 || p >= length s = Nothing
| otherwise = Just (s !! p)
注意:我没有 运行 这段代码 - 只是模拟了文档(现在没有 ghc)
更新
可能是
<s>storageAtPos = \p -> (p^.storage)^?(ix $ p^.pos)</s>
有效,但同样 - 我现在没有 ghc 来测试 - 正如@Gurkenglas 指出的那样,这不是 Prism
编辑:我误解了问题,原答案在最后。
我不知道有什么组合器可以满足您的需求,所以我写了一个。
(^>>=) :: Lens' s a -> (a -> Lens' s b) -> Lens' s b
-- Lens' s a -> (a -> (b -> f b) -> s -> f s) -> (b -> f b) -> s -> f s
-- (That previous line disregards a forall and the Functor constraints)
(x ^>>= f) btofb s = f (s ^. x) btofb s
省略类型签名并询问 ghci 应该会给我们最通用的类型签名,所以这里是:
:t (^>>=)
Getting a s a -> (a -> t1 -> s -> t) -> t1 -> s -> t
Getting 的文档:"When you see this in a type signature it indicates that you can pass the function a Lens, Getter, Traversal, Fold, Prism, Iso, or one of the indexed variants, and it will just "做正确的事"。"
右侧同样通用,允许Traversals/Prisms/etc..
请注意,如果指针不指向自身,这只会产生合法的 lenslikes。
现在应用这个组合器 - 你想要的组合是:
position ^>>= \p -> storage . ix p
这样算出来是个遍历,看原回答
或者,使用另一个我喜欢的组合器:
let (f .: g) x = f . g x in position ^>>= (storage .: ix)
任何带有一些中缀声明的,你甚至可以去掉那些括号。
(这个原始答案假设 position :: Int
局部遮挡位置镜头。)
我们不知道列表在那个位置是否有值,所以这不是Lens',而是Traversal',它代表"traversing over any number of values"而不是"lensing onto one value"。
storage . ix position :: Traversal' Example Int
(^?) 将 return 遍历的第一个值(如果有的话),因此如果该位置有效,则该术语将为您提供 Int,否则为 Nothing。
(^? storage . ix position) :: Example -> Maybe Int
这个部分版本将假定该位置有效,如果无效则崩溃。
(^?! storage . ix position) :: Example -> Int
(%~),将右边的函数应用到左边遍历的所有东西,不仅适用于 Lenses,也适用于所有 Traversals。 (每个镜头都是聪明的 ekmett-trickery 的遍历,并且可以插入遍历可以到达的任何地方。)
storage . ix position %~ (+1) :: Example -> Example
如果您绝对必须使用 Lens,那么如果您尝试在无效位置应用这些部分术语,它们中的任何一个都会崩溃。
singular $ storage . ix position :: Lens' Example Int
storage . singular (ix position) :: Lens' Example Int
PS:您的记录看起来可能需要拉链:如果您只是逐步移动 forward/backward,那么如果您跟踪当前位置左侧的值列表、当前位置的值和当前位置右侧的值列表,而不是所有值的列表和您在其中的位置。如需更多乐趣,请查看 Control.Lens.Zipper,但这些都经过优化以优雅地嵌套多层拉链。
看来,实现这个最简单的方法是使用镜头编写getter和setter,然后组成一个镜头:
at_position :: Functor f => (Int -> f Int) -> Example -> f Example
at_position = lens get set
where
get :: Example -> Int
get e = fromJust $ e ^? storage . ix (e^.position)
set :: Example -> Int -> Example
set e v = e & storage . ix (e^.position) .~ v
虽然这可能会有所改进,但代码足够清晰,并且不局限于对象结构。
比方说,我有一个包含两个字段的对象:
data Example = Example { _position :: Int
, _storage :: [Int]}
我如何构建聚焦于 storage
内的 position
元素的镜头?
此外,是否可以将通过镜头修改的 position
值限制在基于 storage
大小的范围内?
似乎 alongside 可以以某种方式使用,因为 Example
与元组同构,但我无法理解这样做的方法。
我不确定如何表述这个问题,所以找不到太多相关信息。
我认为棱镜应该更适合这种情况,因为 pos
可能是一个不大于列表长度的整数,或者是负数。
我想你可以使用类似 docu for prisms provide
的东西nat :: Prism' Integer Natural nat = prism toInteger $ \ i -> if i < 0 then Left i else Right (fromInteger i)
storageAtPos Prism' Example Int
storageAtPos = prism $ aux
where aux (Example p s) | p < 0 || p >= length s = Nothing
| otherwise = Just (s !! p)
注意:我没有 运行 这段代码 - 只是模拟了文档(现在没有 ghc)
更新
可能是
<s>storageAtPos = \p -> (p^.storage)^?(ix $ p^.pos)</s>
有效,但同样 - 我现在没有 ghc 来测试 - 正如@Gurkenglas 指出的那样,这不是 Prism
编辑:我误解了问题,原答案在最后。
我不知道有什么组合器可以满足您的需求,所以我写了一个。
(^>>=) :: Lens' s a -> (a -> Lens' s b) -> Lens' s b
-- Lens' s a -> (a -> (b -> f b) -> s -> f s) -> (b -> f b) -> s -> f s
-- (That previous line disregards a forall and the Functor constraints)
(x ^>>= f) btofb s = f (s ^. x) btofb s
省略类型签名并询问 ghci 应该会给我们最通用的类型签名,所以这里是:
:t (^>>=)
Getting a s a -> (a -> t1 -> s -> t) -> t1 -> s -> t
Getting 的文档:"When you see this in a type signature it indicates that you can pass the function a Lens, Getter, Traversal, Fold, Prism, Iso, or one of the indexed variants, and it will just "做正确的事"。"
右侧同样通用,允许Traversals/Prisms/etc..
请注意,如果指针不指向自身,这只会产生合法的 lenslikes。
现在应用这个组合器 - 你想要的组合是:
position ^>>= \p -> storage . ix p
这样算出来是个遍历,看原回答
或者,使用另一个我喜欢的组合器:
let (f .: g) x = f . g x in position ^>>= (storage .: ix)
任何带有一些中缀声明的,你甚至可以去掉那些括号。
(这个原始答案假设 position :: Int
局部遮挡位置镜头。)
我们不知道列表在那个位置是否有值,所以这不是Lens',而是Traversal',它代表"traversing over any number of values"而不是"lensing onto one value"。
storage . ix position :: Traversal' Example Int
(^?) 将 return 遍历的第一个值(如果有的话),因此如果该位置有效,则该术语将为您提供 Int,否则为 Nothing。
(^? storage . ix position) :: Example -> Maybe Int
这个部分版本将假定该位置有效,如果无效则崩溃。
(^?! storage . ix position) :: Example -> Int
(%~),将右边的函数应用到左边遍历的所有东西,不仅适用于 Lenses,也适用于所有 Traversals。 (每个镜头都是聪明的 ekmett-trickery 的遍历,并且可以插入遍历可以到达的任何地方。)
storage . ix position %~ (+1) :: Example -> Example
如果您绝对必须使用 Lens,那么如果您尝试在无效位置应用这些部分术语,它们中的任何一个都会崩溃。
singular $ storage . ix position :: Lens' Example Int
storage . singular (ix position) :: Lens' Example Int
PS:您的记录看起来可能需要拉链:如果您只是逐步移动 forward/backward,那么如果您跟踪当前位置左侧的值列表、当前位置的值和当前位置右侧的值列表,而不是所有值的列表和您在其中的位置。如需更多乐趣,请查看 Control.Lens.Zipper,但这些都经过优化以优雅地嵌套多层拉链。
看来,实现这个最简单的方法是使用镜头编写getter和setter,然后组成一个镜头:
at_position :: Functor f => (Int -> f Int) -> Example -> f Example
at_position = lens get set
where
get :: Example -> Int
get e = fromJust $ e ^? storage . ix (e^.position)
set :: Example -> Int -> Example
set e v = e & storage . ix (e^.position) .~ v
虽然这可能会有所改进,但代码足够清晰,并且不局限于对象结构。