2009-05-08 5 views
23

Die Filterklasse der Funktionen nimmt eine Bedingung (a -> Bool) und wendet sie beim Filtern an.Wie kombinieren Sie Filterbedingungen

Was ist der beste Weg, einen Filter zu verwenden, wenn Sie mehrere Bedingungen haben?

Benutzte die applikative Funktion liftA2 anstelle von liftM2 weil ich aus irgendeinem Grund nicht verstanden habe, wie liftM2 in reinem Code funktioniert.

Antwort

29

Die liftM2 combinator kann im Reader Monade verwendet werden, dies zu tun in einer ‚funktionalen‘ Art und Weise:

import Control.Monad 
import Control.Monad.Reader 

-- .... 

filter (liftM2 (&&) odd (> 100)) [1..200] 

Hinweis, dass die Einfuhren sind wichtig; Control.Monad.Reader stellt die Monad (e ->) -Instanz zur Verfügung, mit der das alles funktioniert.

Der Grund, warum dies funktioniert, ist der Leser monad ist nur (e ->) für einige Umgebung e. Ein boolesches Prädikat ist also eine 0-wertige monadische Funktion, die bool in einer ihrem Argument entsprechenden Umgebung zurückgibt. Wir können dann liftM2 verwenden, um die Umgebung über zwei solche Prädikate zu verteilen.

Oder einfacher ausgedrückt, liftM2 wird ein bisschen so handeln, wenn die Arten funktionieren:

liftM2 f g h a = f (g a) (h a) 

Sie können auch eine neue combinator definieren, wenn Sie diese einfach an die Kette in der Lage sein wollen, und/oder wollen nicht zu verwirren mit liftM2:

(.&&.) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool) 
(.&&.) f g a = (f a) && (g a) 
-- or, in points-free style: 
(.&&.) = liftM2 (&&)  

filter (odd .&&. (> 5) .&&. (< 20)) [1..100] 
+3

Beide Beispiele arbeiten auf GHC 7.6.3, auch wenn Sie 'Control.Monad.Reader' nicht importieren. – sjakobi

+2

Ich kann 'liftM2' kann (heutzutage) wie folgt ersetzt werden:' filter ((&&) <$> ungerade <*> (> 100)) [1.200] '. Was gleich ist, aber schöner. :) Es erfordert auch nur "Control.Applicative" und keine vollständigen Monaden. ... Obwohl ich mich immer noch frage, welcher Operator AND mehr als zwei Boolesche Funktionen zulässt ... – Evi1M4chine

15

Nun können Sie Funktionen kombinieren Sie jedoch in Haskell wollen (solange die Typen korrekt sind) und Lambda-Ausdrücke verwenden Sie nicht einmal Ihre Prädikatfunktion nennen müssen, das heißt,

filter (\x -> odd x && x > 100) [1..200] 
9

Lassen Sie uns sagen, dass Ihre Bedingungen in einer Liste gespeichert werden genannt conditions. Diese Liste hat den Typ [a -> Bool].

Um alle Bedingungen auf einen Wert anwenden x, können Sie map verwenden:

map ($ x) conditions 

Dies gilt jede Bedingung zu x und gibt eine Liste von Bool. Zur Reduzierung dieser Liste in einem einzigen boolean, Wahr, wenn alle Elemente wahr sind, und ansonsten False, können Sie die and Funktion:

and $ map ($ x) conditions 

Jetzt haben Sie eine Funktion, die alle Bedingungen vereint. Geben wir ihm einen Namen:

combined_condition x = and $ map ($ x) conditions 

dieser Funktion kann der Typ hat a -> Bool, also können wir es in einem Aufruf von filter verwenden:

filter combined_condition [1..10] 
+2

Vorsicht mit ($ x) im Gegensatz zu ($ x), als würde man Template Haskell aus irgendeinem anderen Grund einschalten, wird das $ x plötzlich wie ein Spleiß aussehen . –

+8

Sie haben die 'all'-Funktion entdeckt:' filter (alle Bedingungen) [1..10] ' –

+1

Es gibt auch die Funktion any, je nachdem, wie Sie die Prädikate kombinieren wollen: any p = or. Karte p; alle p = und. Karte p; –

2

Wenn Sie eine Liste von Filterfunktionen des Typs haben a -> Bool und möchten sie zu einer einzigen präzisen Filterfunktion desselben Typs kombinieren, können wir Funktionen schreiben, die wir einfach machen. Welche der beiden unten genannten Funktionen Sie verwenden, hängt vom Filterverhalten ab, das Sie benötigen.

anyfilt :: [(a -> Bool)] -> (a -> Bool) 
anyfilt fns = \el -> any (\fn -> fn el) fns 

allfilt :: [(a -> Bool)] -> (a -> Bool) 
allfilt fns = \el -> all (\fn -> fn el) fns 

anyfilt kehrt wahr, wenn eine der Filterfunktionen wahr zurückgeben und falsch, wenn alle Filterfunktionen false zurück. allfilt wird True zurückgeben, wenn alle Filterfunktionen Wahr und false zurückgeben, wenn eine der Filterfunktionen false zurückgibt. Beachten Sie, dass Sie beide Funktionen nicht η reduzieren können, da die Referenzen auf fns auf der RHS innerhalb anonymer Funktionen sind.

es wie folgt verwendet:

filterLines :: [String] -> [String] 
filterLines = let 
    isComment = isPrefixOf "# " 
    isBlank = (==) "" 
    badLine = anyfilt([isComment, isBlank]) 
    in filter (not . badLine) 

main = mapM_ putStrLn $ filterLines ["# comment", "", "true line"] 
--> "true line"