我有一个记录列表,需要一个函数在列表中搜索具有给定名称的记录,并修改此记录的值 OR 如果没有记录匹配,则将新记录附加到结果中名单。到目前为止,这是我的代码:
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
函数,或者库中是否有相同的东西?
更新:以下是实验来源的要点:https://gist.github.com/SKoschnicke/5795863
答案 0 :(得分: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
答案 1 :(得分:2)
因此,_find
实际上不是Traversal
:
> [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
现在,假设“更直接的方式”是一些聪明的Traversal
:不,这是不可能的,Traversal
s不能修改遍历事物的结构。
答案 2 :(得分: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 :)