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-construct)?另外,我是否必须编写_find函数或者库中是否有等效的东西?

更新:这是实验源的要点:https ://gist.github.com/SKoschnicke/5795863

4

3 回答 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
于 2013-06-13T13:45:09.227 回答
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的:不,这是不可能的,Traversals 不能修改被遍历事物的结构。

于 2013-06-14T11:38:44.673 回答
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 :)
于 2013-06-16T00:23:05.013 回答