2013-06-13 7 views
8

Ich habe eine Liste von Datensätzen und benötigen eine Funktion, die die Liste nach einem Datensatz mit einem bestimmten Namen durchsucht und den Wert ändert von diesem Datensatz OR Wenn kein Datensatz übereinstimmt, fügen Sie einen neuen Datensatz der resultierenden Liste hinzu. Hier ist mein Code so weit:Ändern Element der Liste, wenn es gegen eine Bedingung hält oder eine neue hinzufügen, wenn nicht, mit Data.Lens

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

Es funktioniert gut, aber ich frage mich, ob es ein direkterer Weg, dies zu schreiben Data.Lens mit (ohne if -Konstrukt)? Muss ich auch die _find Funktion schreiben oder gibt es etwas Äquivalentes in der Bibliothek?

aktualisieren: Hier ist ein Wesentlicher Inhalt der Quelle zu experimentieren ist: https://gist.github.com/SKoschnicke/5795863

Antwort

2

Wie wäre:

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 :) 
+0

Also, ein 'Prism' lässt mich die Liste ändern? Ich denke, dass ich das testen muss, um komplett zu verstehen, werde das morgen tun! –

+0

Funktioniert gut! Aber soweit ich das verstehe, ist 'pos' mit' filtered' kein gültiges 'Traversal' mehr, oder? Daher ist es nicht generell mit anderen Traversalen zusammensetzbar. –

+0

Es ist in der Tat kein gültiges 'Traversal', wenn Sie es verwenden, um das 'name'-Feld zu ändern und somit das Prädikat nicht mehr halten (" Regeln "können brechen, wie in Matvey's Antwort gezeigt), aber ansonsten ist es mit anderen kompostierbar Durchquerungen. – yairchu

2

Ich weiß nicht, aber Sie können einige wie

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 
+0

Das von der Wiederholung entledigt, danke! –

2

So schreiben, ist _find nicht wirklich ein 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] 

, dass der gleiche Sinn filtered ist, ist nicht ein travers al.

Erste kann Teil mit filtered nachgeahmt werden (es ist hier in Ordnung, da Fold hat keine Gesetze):

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

Jetzt „direkteren Weg“ ist einige clevere Traversal Annahme: nein, das ist nicht möglich, Traversal s kann die Struktur des durchlaufenen Objekts nicht ändern.

+0

Ich denke, ich muss etwas mehr über Objektive lesen, um das zu verstehen, aber danke, dass du darauf hingewiesen hast! Ich nehme an, es gibt nichts wie ein "Traversal", das * die Struktur des durchquerten Dinges * verändern kann? –

+0

Das stimmt, das gibt es nicht. Das Problem ist, dass Sie keine schönen Gesetze dafür haben können. –