编写更复杂的遍历(镜头)

时间:2016-12-20 18:15:07

标签: haskell traversal lens

我正在不断深入探索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

注意它如何使用缓冲区上下文中的文本信息在光标上创建镜头; (另外,我很想知道任何更干净的方法,而不是制作定制镜头,如果有人有建议,我发现自己做了很多)。

所以现在我有多个游标我需要将其转换为Traversal',但当然我无法使用lens getter setter方法定义遍历;环顾四周如何定义遍历我读了tutorial;其中陈述如下:

  

问题:如何创建遍历?

     

答案:创建原始遍历有三种主要方法:

     
      
  • traverse是一个Traversal',你可以获得任何实现Traversable
  • 的类型   
  • 每个镜头'也会打字检查为Traversal'
  •   
  • 您可以使用Template Haskell使用makePrisms生成Traversal,因为每个Prism'也是Traversal'(本教程未涵盖)
  •   

这些方法都没有真正帮助到这里;我也看到了使用applicative样式创建遍历的样式,但它总是让我感到有点困惑,我真的不知道如何在这种情况下使用它来获得我想要的东西。

我想我可以编写一个Lens' Buffer [Cursor]映射到setter中的游标以执行验证,然后遍历该列表,但我认为必须有一种方法可以在遍历后将其烘焙到遍历中(当每个单个元素被聚焦时)以某种方式。也许有一种更好的方法可以完全做到这一点;

仍然希望尽可能多地了解遍历,所以任何解释都值得赞赏!谢谢!

编辑: @dfeuer指出,当你进行这种验证时,你最终会得到无效镜头,我真的很喜欢它提供的清晰界面来在镜头内完成它们;据我所知,由于验证是幂等的,它不会引起任何实际问题,但我愿意接受有关如何做得更好的建议。

2 个答案:

答案 0 :(得分:2)

我的建议是直接使用Functor / Applicative镜头表示来做这类事情。

要编写非类型更改(简单)LensTraversal,您需要编写一个函数,将函数k :: a -> f a作为参数,并将结构{{1然后生成s

f s是一种广义修改函数,它表示镜头用户想要对镜头聚焦的数据做出的改变。但由于k不仅仅是k类型,而是类型a -> a,因此它还允许带有"结果"超出更新范围,例如,更新之前字段的值(如果你使用状态monad为f,那么你可以在更新函数中将状态设置为字段的旧值并读出它当你之后运行状态monad时。)

我们在以下代码中的方法是更改​​此修改函数,在返回新值之前执行一些钳位:

a -> f a

然后我们可以将一个非验证镜头变成一个验证镜头(注意,如评论中所述,这个验证镜头不是一个守法镜头):

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

现在我们可以使用与之前相同的方法:因为遍历已经进行了#34;映射"对我们来说,代码是相同的,除了我们现在-- 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 上有Applicative f约束,因为我们需要f

Traversal'

答案 1 :(得分:0)

curs :: Lens' Buffer Cursor成为curs :: Lens' Buffer [Cursor]时,您构建法律无视cursor :: Traversal' Buffer Cursor的任务可以分为构建一个法律 - 无视Lens' Buffer [Cursor],它会限制检查,并且转向任何Lens' s [a]Traversal' s a

第一个可以由你解决:你做你已经做过的事情,但检查[Cursor]的每个元素的界限。

第二个是遵守法律的要求,因此你可以在你的帖子中没有那么多评论的情况下期待答案:

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