如何使用Control.Lens来更新列表的第i个元素?

13

我有一些类似的数据类型

data Outer = Outer { _list :: [ Inner ] }
data Inner = Inner { _bool :: Bool }

使用 Control.Lens,我可以像这样访问第 i 个内部的 _bool(位于“State Outer”单子中)

boolValue <- gets (^. list . to (!! i) . inner)

我希望也能够通过类似以下的方式更新该值:

list ^. (to (!! i)) ^. inner %= True

据我所理解,'to'函数仅创建一个getter,而非真正的lens,不能同时用作getter或setter。

那么,我该如何将 (!! i) 转换为lens以便更新此字段?

2个回答

17

你无法将(!!)转换为除了Getter之外的任何类似于Lens的东西,但是有一个函数可以做到这种事情:ix,用于访问索引处的元素。实际上,它是一个Traversal而不是一个Lens,这意味着如果索引超出范围,它会失败。但只要索引在列表中,就能正常工作。

然而,还有另一个问题 -- (^.)也是一个仅用于获取值的运算符。它与例如(%=)不兼容,后者将一个类似于Lens的东西作为其第一个参数。而(%=)是为将函数映射到现有值;如果您只想设置值,可以使用(.=)。所以您可能想要像这样:

list . ix i . inner .= True

实际上有一个能做到这个的函数 - 它被称为 upon - 但它使用了神奇的邪恶黑魔法,你不应该使用它,至少不用于这个问题(也可能不用于任何真正的代码)。


你能澄清一下 ixelement 之间的区别吗?我选择了 Gabriel 的 element,因为它似乎更简单,只需要一个 Int 而不是一个 Index。从你提供的文档中,ix 允许的范围更广泛 - 它是否严格更通用? - ajp
1
我没有想到element。并不是说一个比另一个更一般化... ix是一个类型类,它有许多针对特定索引类型进行索引的实例(例如,使用Int的列表,带有其索引类型的Map,具有其域的函数)。element接受任何可遍历的类型,并使用Int索引从左到右计数。在这种情况下,它们碰巧重合。 - shachaf
我还没有测试过,但我猜想对于这种特殊情况,“ix”可能更有效率(主要是因为我还没有找到一种方法使“Indexing”生成良好的代码... :-( 欢迎其他人尝试)。它也是一个有点临时性的类。 - shachaf

10
使用element,它是一个指向特定列表元素的遍历器
list . element i . inner %= True :: Outer -> Outer

如果您想获取列表元素,必须使用 Maybe,因为列表元素可能不存在:

myList :: Outer

myList ^? list . element i . inner :: Maybe Bool

请注意,%=提供了一个用于修改状态的State操作,即... :: State Outer ()。(此外,我认为问题在这里应该是.=。) - shachaf
是的,我指的是%~,但我认为你的答案更好。 - Gabriella Gonzalez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接