Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change element of list if it holds against some condition or add a new one if not, using Data.Lens

I have a list of records and need a function which searches the list for a record with a given name and modify the value of this record OR if no record matches append a new record to the resulting list. Here is my code so far:

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)]

It works fine, but I'm wondering if there is a more direct way of writing this using Data.Lens (without the if-construct)? Also, do I have to write the _find function or is there something equivalent in the library?

Update: Here is a Gist of the source to experiment: https://gist.github.com/SKoschnicke/5795863

like image 312
Sven Koschnicke Avatar asked Jun 13 '13 12:06

Sven Koschnicke


3 Answers

How about:

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 :)
like image 95
yairchu Avatar answered Nov 14 '22 10:11

yairchu


I don't know, but you can write some like

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
like image 33
josejuan Avatar answered Nov 14 '22 10:11

josejuan


So, _find is not actually a 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]

That's the same sense filtered is not a traversal.

Getting part can be mimicked with filtered (it's okay here since Fold does not have any laws):

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

Now, assuming "more direct way" is some clever Traversal: no, that's not possible, Traversals can not modify the structure of the traversed thing.

like image 2
Matvey Aksenov Avatar answered Nov 14 '22 12:11

Matvey Aksenov