Haskell 使用一级透镜来创建复杂透镜

suk*_*mel 3 haskell haskell-lens

假设我有一个包含两个字段的对象:

data Example = Example { _position :: Int
                       , _storage  :: [Int]}
Run Code Online (Sandbox Code Playgroud)

如何构建聚焦于position内部元素的镜头storage?

另外,是否可以将position通过镜头修改的值限制在基于尺寸的范围内storage?

似乎可以以某种方式使用旁边Example,因为它与元组同构,但我无法理解这样做的方法。

我不知道如何表达这个问题,所以我无法找到很多相关信息。

Gur*_*las 5

编辑:我误解了这个问题,原始答案在最后。

我不知道有哪个组合器可以满足您的要求,所以我写了一个。

(^>>=) :: 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
Run Code Online (Sandbox Code Playgroud)

省略类型签名并询问 ghci 应该会给我们最通用的类​​型签名,所以这里是:

:t (^>>=)
Getting a s a -> (a -> t1 -> s -> t) -> t1 -> s -> t
Run Code Online (Sandbox Code Playgroud)

Getting 的文档:“当您在类型签名中看到这一点时,它表明您可以向函数传递 Lens、Getter、Traversal、Fold、Prism、Iso 或索引变体之一,并且它只会“做正确的事情” ”。

右侧同样通用,允许遍历/棱镜/等等。

请注意,如果指针不是指向其自身,则这只会产生合法的透镜状图像。

现在应用这个组合器 - 您想要的组合是:

position ^>>= \p -> storage . ix p
Run Code Online (Sandbox Code Playgroud)

这是一个遍历,请参阅原始答案。

或者,使用我喜欢的另一个组合器:

let (f .: g) x = f . g x in position ^>>= (storage .: ix)
Run Code Online (Sandbox Code Playgroud)

任何带有一些中缀声明的东西,你甚至可以去掉那些括号。


(这个原始答案假设position :: Int局部阴影定位镜头。)

我们不知道列表在该位置是否有值,因此这不是“Lens”,而是“Traversal”,它代表“遍历任意数量的值”而不是“镜头到一个值”。

storage . ix position :: Traversal' Example Int
Run Code Online (Sandbox Code Playgroud)

(^?) 将返回遍历的第一个值(如果有),因此如果该位置有效,则此项将为您提供 Int,如果无效,则为 Nothing。

(^? storage . ix position) :: Example -> Maybe Int
Run Code Online (Sandbox Code Playgroud)

该部分版本将假设该位置有效,如果无效则崩溃。

(^?! storage . ix position) :: Example -> Int
Run Code Online (Sandbox Code Playgroud)

(%~) 将右侧的函数应用于左侧遍历的所有内容,不仅适用于镜头,而且适用于所有遍历。(每个镜头都是巧妙的 ekmett 技巧的遍历,并且可以插入遍历可以到达的任何地方。)

storage . ix position %~ (+1) :: Example -> Example
Run Code Online (Sandbox Code Playgroud)

如果您绝对必须使用镜头,那么如果您尝试将它们应用在无效位置,则这些部分术语中的任何一个都会崩溃。

singular $ storage . ix position :: Lens' Example Int
storage . singular (ix position) :: Lens' Example Int
Run Code Online (Sandbox Code Playgroud)

PS:你的记录看起来你可能需要拉链:如果你只增量地向前/向后移动,如果你跟踪当前位置左侧的值列表,你会做更少的臭(!!)事情,当前位置的值以及当前位置右侧的值列表,而不是所有值和您在其中的位置的列表。想要获得更多镜头乐趣,请查看Control.Lens.Zipper,但它们经过了优化,可以优雅地嵌套多个级别的拉链。