按值修改 Haskell 嵌套记录
Modify Haskell nested record by value
假设我有一个嵌套结构如下:
data Bar = Bar { _id :: Integer, _bars :: [Bar] }
data Foo = Foo { _bars :: [Bar] }
我有一个 Foo
和一堆 Bars
以及各种 id
s:
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 []]]
作为另一个例子,为了好玩,让我们使用 cosmos
从 Bar
.
中提取所有 Bar 5
fives :: Bar -> [Bar]
fives = toListOf $ cosmos.filtered (views lbl (== 5))
假设我有一个嵌套结构如下:
data Bar = Bar { _id :: Integer, _bars :: [Bar] }
data Foo = Foo { _bars :: [Bar] }
我有一个 Foo
和一堆 Bars
以及各种 id
s:
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 []]]
作为另一个例子,为了好玩,让我们使用 cosmos
从 Bar
.
Bar 5
fives :: Bar -> [Bar]
fives = toListOf $ cosmos.filtered (views lbl (== 5))