7

我正在尝试对包含元素列表的嵌套数据结构进行操作。在尝试了各种方法后,我最终决定将镜头作为最好的方法。它们非常适合查找和修改结构的特定元素,但到目前为止,我对如何添加新元素感到困惑。

从我读过的内容来看,我不能在技术上使用遍历,因为它违反遍历定律将新元素插入到列表中,并且假设我什至可以首先弄清楚如何使用遍历来做到这一点(我对 Haskell 的使用仍然相当薄弱,并且镜头包中大多数东西的类型签名让我头晕目眩)。

具体来说,我想要完成的是,在与特定选择器匹配的元素列表中找到一些元素,然后在匹配的元素之前或之后插入一个新元素(在之前或之后的函数的不同参数匹配)。Control.Lens 是否已经有一些东西可以完成我正在尝试做的事情,并且我对类型签名的理解太弱以至于看不到它?有没有更好的方法来完成我想要做的事情?

如果我只是想在列表的开头或结尾添加一个新元素,那将是相当微不足道的,但是将它插入到中间的某个特定位置是困难的部分。在我编写的一些镜头前代码中,我使用折叠来完成我想要的,但它开始在结构的更深嵌套部分变得粗糙(例如折叠内的折叠内的折叠)所以我转向 Control.Lens 试图解开一些混乱。

4

3 回答 3

7

使用镜头

如果我们从知道函数id可以像镜头一样使用开始:

import Control.Lens
> [1,2,3,4] ^. id
[1,2,3,4]

然后我们可以继续讨论如何修改列表:

> [1,2,3,4] & id %~ (99:)
[99,1,2,3,4]

以上允许在列表的开头插入。要专注于列表的后半部分,我们可以使用_tailControl.Lens.Cons模块

> [1,2,3,4] ^. _tail
[2,3,4]
> [1,2,3,4] & _tail %~ (99:)
[1,99,2,3,4]

现在将其推广到第 n 个位置

> :{
let
_drop 0 = id
_drop n = _tail . _drop (n - 1)
:}
> [1,2,3,4] ^. _drop 1
[2,3,4]
> [1,2,3,4] & _drop 0 %~ (99:)
[99,1,2,3,4]
> [1,2,3,4] & _drop 1 %~ (99:)
[1,99,2,3,4]

最后一步是使用我们可以使用的Cons实例cons将其推广到所有类型或<|

> [1,2,3,4] & _drop 1 %~ (99<|)
[1,99,2,3,4]
> import Data.Text
> :set -XOverloadedStrings
> ("h there"::Text) & _drop 1 %~ ('i'<|)
"hi there"
于 2015-10-23T05:40:31.333 回答
2

对您的问题的一些评论:

回答问题:可能有一种方法可以做你想做的事。Lens 库非常通用。没有一种简单或明显的方法来实现它。我认为它将涉及partsOf组合器,但我不确定。

镜头评论:镜头库真的很酷,可以应用到数量惊人的问题。当我学习图书馆时,我最初的诱惑是尝试将所有内容都放入镜头访问或突变中。我发现最好使用 lens 库来挖掘我的复杂数据结构,但是一旦我有了一个简单的元素,最好使用我已经知道的更传统的函数技术,而不是将 Lens 库延伸到它有用的地方限制。

您没有要求的建议:将元素插入列表中间是一个坏主意。并不是说它不能完成,但它最终可能是一个 O(n^2) 操作。(另请参阅此 StackOverflow 答案。)Zip 列表或其他一些功能数据结构可能是一个更好的主意。作为附带的好处,这些结构中的一些可以成为At允许使用部分透镜组合器插入和删除的类的实例。

于 2013-08-28T07:37:04.433 回答
2

我认为一种简单的方法可以解决以下问题:

  • 的一个函数[a] -> SomeAddtionalData -> [a],它基本上负责使用一些特定的数据将列表转换为另一个列表。这是您从列表中添加/删除元素并获取新列表的地方
  • 使用 lense 从一些嵌套数据结构中提取列表,将该列表传递给上面定义的函数,使用 lense 将返回的列表设置在嵌套数据结构中。

你的最后一段是关于当你尝试使用像 Lens 这样的通用抽象做太多事情时会发生什么的指示。这些通用抽象适用于某些通用目的,其他一切都特定于您的问题,应该围绕普通的旧函数设计(至少在最初,稍后在您的项目中,您可能会在您的代码库中找到一些通用模式,可以使用类型类等)。

于 2013-08-28T04:09:37.647 回答