按值修改 Haskell 嵌套记录

Modify Haskell nested record by value

假设我有一个嵌套结构如下:

data Bar = Bar { _id :: Integer, _bars :: [Bar] }
data Foo = Foo { _bars :: [Bar] }

我有一个 Foo 和一堆 Bars 以及各种 ids:

foo = Foo [Bar 1 [Bar 2], Bar 3 [Bar 4, Bar 5]]

如何修改 foo,使 Bar 5 变为 Bar 6

我知道我使用 fclabels 来做这样的事情:

mkLabel ''Foo
mkLabel ''Bar
modify bars (\bars -> ...) foo

但是条可以无限嵌套。如何定位和修改指定ID的Bar

是的,lens 可以做到。 Control.Lens.Plated 模块包含用于 "Scrap Your Boilerplate" 风格编程的工具,具有像您的 Bar 这样的自相似结构。这个想法非常简单:您解释如何找到节点的直接子节点(通过编写 Traversal' a a),然后库递归地将遍历应用到整个结构。

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens    

data Bar = Bar { _lbl :: Int, _bars :: [Bar] } deriving (Show)

makeLenses ''Bar

instance Plated Bar where
    plate = bars.traverse

(如果你不想自己实现plate,你可以导出Data并将instance留空。)

transform :: Plated a => (a -> a) -> a -> a接受一个修改单个节点的函数并将其应用于整个结构。

fiveToSix :: Bar -> Bar
fiveToSix = transform go
    where go bar
            | bar^.lbl == 5 = bar & lbl .~ 6
            | otherwise = bar

使用您问题中的示例:

ghci> let bars = [Bar 1 [Bar 2 []], Bar 3 [Bar 4 [], Bar 5 []]]
ghci> map fiveToSix bars
[Bar 1 [Bar 2 []], Bar 3 [Bar 4 [], Bar 6 []]]

作为另一个例子,为了好玩,让我们使用 cosmosBar.

中提取所有 Bar 5
fives :: Bar -> [Bar]
fives = toListOf $ cosmos.filtered (views lbl (== 5))