使用Data.Lens,如果列表元素满足某些条件,则更改该元素,否则添加新元素

8

我有一个记录列表,需要一个函数来搜索具有给定名称的记录,并修改该记录的值或者如果没有匹配的记录,则将新记录附加到结果列表中。这是我的代码:

import Control.Lens
import Control.Applicative ((<$>), pure)
import Data.List (any)

data SomeRec = SomeRec { _name :: String, _val :: Int }
$(makeLenses ''SomeRec)

_find :: (a -> Bool) -> Simple Traversal [a] a
_find _ _ [] = pure []
_find pred f (a:as) = if pred a
                        then (: as) <$> f a
                        else (a:) <$> (_find pred f as)

changeOrCreate :: [SomeRec] -> String -> (Int -> Int) -> [SomeRec]
changeOrCreate recs nameToSearch valModifier = 
  if (any (\r -> r^.name == nameToSearch) recs)
    then over (_find (\r -> r^.name == nameToSearch)) (over val valModifier) recs
    else recs ++ [SomeRec nameToSearch (valModifier 0)]

它可以正常工作,但我想知道是否有更直接的方法使用 Data.Lens(不使用 if 构造)来编写它?此外,我是否必须编写 _find 函数,还是库中存在等效的函数?
更新:这里有一个源代码 Gist 供您实验:https://gist.github.com/SKoschnicke/5795863
3个回答

2
我不知道,但你可以写一些类似的东西。
changeOrCreate [] n f = [SomeRec n (f 0)]
changeOrCreate (r:rs) n f | r^.name == n = (over val f) r:rs
                          | otherwise    = r: changeOrCreate rs n f

2

所以,_find实际上不是一个遍历器

> [1..10] & over (_find odd) succ . over (_find odd) succ
[2,2,4,4,5,6,7,8,9,10]
> [1..10] & over (_find odd) (succ . succ)
[3,2,3,4,5,6,7,8,9,10]

这和filtered不是一个遍历的感觉。

可以用filtered来模拟获取部分内容(在这里是可以的,因为Fold没有任何法律限制):

> [1..10] ^? _find even
Just 2
> [1..10] ^? _find (> 20)
Nothing
> [1..10] ^? folded . filtered even
Just 2
> [1..10] ^? folded . filtered (> 20)
Nothing

假设“更直接的方式”是某种聪明的遍历方法:不,这是不可能的,遍历无法修改所遍历的内容结构。

我认为我需要再多了解一些有关镜头的知识才能理解这个问题,但是感谢您指出这一点!我假设没有像“遍历”这样可以改变所遍历对象结构的东西吗? - Sven Koschnicke
没错,这样的东西是不存在的。问题在于你无法为它制定任何好的规则。 - Matvey Aksenov

2
如何考虑:
changeOrCreate :: String -> (Int -> Int) -> [SomeRec] -> [SomeRec]
changeOrCreate nameToSearch valModifier = 
  pos . val %~ valModifier
  & outside (filtered (not . has pos)) %~ (. newRec)
  where
    pos :: Traversal' [SomeRec] SomeRec
    pos = taking 1 (traversed . filtered (anyOf name (== nameToSearch)))
    newRec = (SomeRec nameToSearch 0 :)

那么,Prism可以让我改变列表?我认为我必须测试一下才能完全理解,明天会做的! - Sven Koschnicke
运行得很好!但是就我所理解的而言,使用filtered后, pos不再是有效的Traversal了,对吧?因此,它通常不能与其他遍历组合使用。 - Sven Koschnicke
如果您使用它来更改“name”字段并因此使谓词不再成立(如Matvey的答案所示),那么它确实不是一个有效的“遍历”,但除此之外,它可以与其他遍历组合。 - yairchu

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