编写更复杂的遍历(镜头)
Writing a more complex Traversal (Lenses)
我正在不断深入研究 Kmett 的镜头;今天我正在尝试编写一些自定义遍历,直到现在我已经设法通过组合现有遍历来创建新遍历来相处,但是我正在做一些更复杂的事情并且卡住了。
我正在写一个文本编辑器,我只是添加了多个光标,最初每个缓冲区都有一个光标并有一个镜头来聚焦它,现在我要概括为一个光标列表并想要遍历名单。诀窍在于,在之前的案例中,我的镜头在 setter 内部进行了一些验证,以确保光标被限制在缓冲区文本的有效范围内。它看起来像这样:
clampCursor :: Text -> Cursor -> Cursor
cursor :: Lens' Buffer Cursor
cursor = lens getter setter
where getter buf = buf^.curs
setter buf new = let txt = buf^.text
in buf & curs .~ clampCursor txt new
注意它如何使用缓冲区上下文中的文本信息在光标上创建一个透镜; (此外,如果有人有建议,我很想听听任何更简洁的方法来做这件事而不是制作定制镜头,我发现自己做了很多)。
所以现在我有多个游标,我需要将其转换为遍历',但是我当然不能使用 lens getter setter
方法定义遍历;四处寻找如何定义遍历我读到了这个tutorial;其中规定如下:
Question: How do I create traversals?
Answer: There are three main ways to create primitive traversals:
- traverse is a Traversal' that you get for any type that implements Traversable
- Every Lens' will also type-check as a Traversal'
- You can use Template Haskell to generate Traversal's using makePrisms since every Prism' is also a Traversal' (not covered in this tutorial)
None 这些方法在这里真的很有帮助;我也看到过使用应用风格创建遍历的风格,但它总是让我有点困惑,我真的不知道在这种情况下我将如何使用它来获得我想要的东西。
我想我可以写一个 Lens' Buffer [Cursor]
映射到 setter 中的游标以执行验证然后遍历该列表,但我认为必须有一种方法来烘焙它以某种方式在遍历之后(当每个元素都被聚焦时)进入遍历。也许有更好的方法来完全做到这一点;
仍在寻找尽可能多的关于遍历的知识,所以任何解释都不胜感激!谢谢!
编辑:
@dfeuer 指出,当您进行此类验证时,您最终会得到无效的镜头,我真的很喜欢它提供的干净界面,可以在镜头内进行这些验证;据我所知,由于验证是幂等的,因此不会引起任何实际问题,但我愿意接受有关如何更好地做到这一点的建议。
随着 curs :: Lens' Buffer Cursor
变为 curs :: Lens' Buffer [Cursor]
,您构建一个无视法律 cursor :: Traversal' Buffer Cursor
的任务可以拆分为构建一个进行边界检查的无视法律 Lens' Buffer [Cursor]
,并将任何 Lens' s [a]
变成 Traversal' s a
.
第一个可能已经被你解决了:你做你已经在做的事情,但是检查 [Cursor]
.
的每个元素的边界
第二个是遵纪守法的请求,因此您本可以期待在您的 post 下没有大量评论的情况下得到答复:
turnIntoTraversal :: Lens' s [a] -> Traversal' s a
turnIntoTraversal l = l . traverse
同样为了好玩,这里尝试直接实现。
cursor :: Traversal' Buffer Cursor
cursor atofa s = (curs . traverse) (fmap (clampCursor $ s ^. text) . atofa) s
我的建议是直接使用镜头的 Functor
/ Applicative
表示来做这种事情。
要编写非类型更改(简单)Lens
或 Traversal
,您需要编写一个函数,该函数将函数 k :: a -> f a
和结构 k :: a -> f a
作为参数=19=] 然后生成 f s
.
k
是一种广义的修正函数,表示镜头使用者想要对镜头聚焦的数据进行的改变。但是因为 k
不是简单的 a -> a
类型,而是 a -> f a
类型,它还允许在更新中携带一个 "result",例如,值更新前的字段(如果你对 f 使用状态 monad,那么你可以在更新函数中将状态设置为字段的旧值,然后当你 运行 状态 monad 时读出它)。
我们在下面的代码中的做法是改变这个修改函数,在返回新值之前执行一些钳位:
-- Takes a cursor update function and returns a modified update function
-- that clamps the return value of the original function
clampCursorUpdate :: Functor f => Text -> (Cursor -> f Cursor) -> (Cursor -> f Cursor)
clampCursorUpdate k = \cur -> fmap (clampCursor txt) (k cur)
然后我们可以把一个非验证镜头变成一个验证镜头(注意,正如评论中所说,这个验证镜头不是一个守法镜头):
-- assuming that _cursor is a lens that accesses
-- the _cursor field without doing any validation
_cursor :: Lens' Buffer Cursor
cursor :: Functor f => (Cursor -> f Cursor) -> Buffer -> f Buffer
cursor k buffer = _cursor (clampCursorUpdate txt k) buffer
where txt = buffer^.text
这种方法很容易推广到遍历。首先,我们通过组合 Lens' Buffer [Cursor]
和 traverse
来编写非验证遍历,这会将其变成 Traversal' Buffer Cursor
:
-- assuming that _cursors is a lens that returns the list of cursors
_cursors :: Lens' Buffer [Cursor]
-- non-validating cursors traversal
_cursorsTraversal :: Traversal' Buffer Cursor
_cursorsTraversal = _cursors . traverse
现在我们可以使用与之前相同的方法:因为遍历已经为我们做了 "mapping",代码是相同的,除了我们现在有一个 Applicative f
约束我们的 f
,因为我们想要 Traversal'
:
cursors :: Applicative f => (Cursor -> f Cursor) -> Buffer -> f Buffer
cursors k buffer = _cursorsTraversal (clampCursorUpdate txt k) buffer
whee txt = buffer^.text
我正在不断深入研究 Kmett 的镜头;今天我正在尝试编写一些自定义遍历,直到现在我已经设法通过组合现有遍历来创建新遍历来相处,但是我正在做一些更复杂的事情并且卡住了。
我正在写一个文本编辑器,我只是添加了多个光标,最初每个缓冲区都有一个光标并有一个镜头来聚焦它,现在我要概括为一个光标列表并想要遍历名单。诀窍在于,在之前的案例中,我的镜头在 setter 内部进行了一些验证,以确保光标被限制在缓冲区文本的有效范围内。它看起来像这样:
clampCursor :: Text -> Cursor -> Cursor
cursor :: Lens' Buffer Cursor
cursor = lens getter setter
where getter buf = buf^.curs
setter buf new = let txt = buf^.text
in buf & curs .~ clampCursor txt new
注意它如何使用缓冲区上下文中的文本信息在光标上创建一个透镜; (此外,如果有人有建议,我很想听听任何更简洁的方法来做这件事而不是制作定制镜头,我发现自己做了很多)。
所以现在我有多个游标,我需要将其转换为遍历',但是我当然不能使用 lens getter setter
方法定义遍历;四处寻找如何定义遍历我读到了这个tutorial;其中规定如下:
Question: How do I create traversals?
Answer: There are three main ways to create primitive traversals:
- traverse is a Traversal' that you get for any type that implements Traversable
- Every Lens' will also type-check as a Traversal'
- You can use Template Haskell to generate Traversal's using makePrisms since every Prism' is also a Traversal' (not covered in this tutorial)
None 这些方法在这里真的很有帮助;我也看到过使用应用风格创建遍历的风格,但它总是让我有点困惑,我真的不知道在这种情况下我将如何使用它来获得我想要的东西。
我想我可以写一个 Lens' Buffer [Cursor]
映射到 setter 中的游标以执行验证然后遍历该列表,但我认为必须有一种方法来烘焙它以某种方式在遍历之后(当每个元素都被聚焦时)进入遍历。也许有更好的方法来完全做到这一点;
仍在寻找尽可能多的关于遍历的知识,所以任何解释都不胜感激!谢谢!
编辑: @dfeuer 指出,当您进行此类验证时,您最终会得到无效的镜头,我真的很喜欢它提供的干净界面,可以在镜头内进行这些验证;据我所知,由于验证是幂等的,因此不会引起任何实际问题,但我愿意接受有关如何更好地做到这一点的建议。
随着 curs :: Lens' Buffer Cursor
变为 curs :: Lens' Buffer [Cursor]
,您构建一个无视法律 cursor :: Traversal' Buffer Cursor
的任务可以拆分为构建一个进行边界检查的无视法律 Lens' Buffer [Cursor]
,并将任何 Lens' s [a]
变成 Traversal' s a
.
第一个可能已经被你解决了:你做你已经在做的事情,但是检查 [Cursor]
.
第二个是遵纪守法的请求,因此您本可以期待在您的 post 下没有大量评论的情况下得到答复:
turnIntoTraversal :: Lens' s [a] -> Traversal' s a
turnIntoTraversal l = l . traverse
同样为了好玩,这里尝试直接实现。
cursor :: Traversal' Buffer Cursor
cursor atofa s = (curs . traverse) (fmap (clampCursor $ s ^. text) . atofa) s
我的建议是直接使用镜头的 Functor
/ Applicative
表示来做这种事情。
要编写非类型更改(简单)Lens
或 Traversal
,您需要编写一个函数,该函数将函数 k :: a -> f a
和结构 k :: a -> f a
作为参数=19=] 然后生成 f s
.
k
是一种广义的修正函数,表示镜头使用者想要对镜头聚焦的数据进行的改变。但是因为 k
不是简单的 a -> a
类型,而是 a -> f a
类型,它还允许在更新中携带一个 "result",例如,值更新前的字段(如果你对 f 使用状态 monad,那么你可以在更新函数中将状态设置为字段的旧值,然后当你 运行 状态 monad 时读出它)。
我们在下面的代码中的做法是改变这个修改函数,在返回新值之前执行一些钳位:
-- Takes a cursor update function and returns a modified update function
-- that clamps the return value of the original function
clampCursorUpdate :: Functor f => Text -> (Cursor -> f Cursor) -> (Cursor -> f Cursor)
clampCursorUpdate k = \cur -> fmap (clampCursor txt) (k cur)
然后我们可以把一个非验证镜头变成一个验证镜头(注意,正如评论中所说,这个验证镜头不是一个守法镜头):
-- assuming that _cursor is a lens that accesses
-- the _cursor field without doing any validation
_cursor :: Lens' Buffer Cursor
cursor :: Functor f => (Cursor -> f Cursor) -> Buffer -> f Buffer
cursor k buffer = _cursor (clampCursorUpdate txt k) buffer
where txt = buffer^.text
这种方法很容易推广到遍历。首先,我们通过组合 Lens' Buffer [Cursor]
和 traverse
来编写非验证遍历,这会将其变成 Traversal' Buffer Cursor
:
-- assuming that _cursors is a lens that returns the list of cursors
_cursors :: Lens' Buffer [Cursor]
-- non-validating cursors traversal
_cursorsTraversal :: Traversal' Buffer Cursor
_cursorsTraversal = _cursors . traverse
现在我们可以使用与之前相同的方法:因为遍历已经为我们做了 "mapping",代码是相同的,除了我们现在有一个 Applicative f
约束我们的 f
,因为我们想要 Traversal'
:
cursors :: Applicative f => (Cursor -> f Cursor) -> Buffer -> f Buffer
cursors k buffer = _cursorsTraversal (clampCursorUpdate txt k) buffer
whee txt = buffer^.text