2009-05-08 17 views
23

La classe de filtre de fonctions prend une condition (a -> Bool) et l'applique lors du filtrage.Comment combiner les conditions de filtre

Quelle est la meilleure façon d'utiliser un filtre lorsque vous avez plusieurs conditions? Lift2 au lieu de liftM2 parce que je ne comprenais pas comment liftM2 fonctionnait dans le code pur.

Répondre

29

Le liftM2 combinateur peut être utilisé dans la monade Reader pour le faire d'une manière « plus fonctionnelle »:

import Control.Monad 
import Control.Monad.Reader 

-- .... 

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

Notez que les importations sont importantes; Control.Monad.Reader fournit l'instance Monad (e ->) qui fait que tout cela fonctionne.

La raison pour laquelle cela fonctionne est la monade du lecteur est juste (e ->) pour un environnement e. Ainsi, un prédicat booléen est une fonction monophique 0-aire retournant booléen dans un environnement correspondant à son argument. Nous pouvons ensuite utiliser liftM2 pour répartir l'environnement sur deux de ces prédicats.

Ou, en termes plus simples, liftM2 agira un peu comme quand les types fonctionnent sur:

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

Vous pouvez également définir une nouvelle Combinator si vous voulez être en mesure de chaîner ces facilement, et/ou ne veulent pas se salir avec 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

Les deux exemples fonctionnent sur GHC 7.6.3 même si vous n'importez pas 'Control.Monad.Reader'. – sjakobi

+2

Je suis 'liftM2' peut (de nos jours) peut être remplacé comme ceci:' filtre ((&&) <$> impaire <*> (> 100)) [1.200] '. Ce qui est pareil, mais plus joli. :) Il nécessite également seulement 'Control.Applicative', et pas de monades complètes. ... Bien que je me demande encore quel opérateur autorise ET plus de deux fonctions booléennes ... – Evi1M4chine

15

Eh bien, vous pouvez combiner les fonctions mais vous voulez en Haskell (tant que les types sont corrects) et l'utilisation lambdas vous n'avez même pas le nom de votre fonction sous-jacente, à savoir

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

Disons que vos conditions sont stockées dans une liste appelée conditions. Cette liste a le type [a -> Bool].

Pour appliquer toutes les conditions à une valeur x, vous pouvez utiliser map:

map ($ x) conditions 

Cette applique chaque condition de x et retourne une liste de Bool. Pour réduire cette liste en une seule booléen, Vrai si tous les éléments sont vrais et faux sinon, vous pouvez utiliser la fonction and:

and $ map ($ x) conditions 

Maintenant vous avez une fonction qui combine toutes les conditions. Donnons-lui un nom:

combined_condition x = and $ map ($ x) conditions 

Cette fonction a le type a -> Bool, afin que nous puissions l'utiliser dans un appel à filter:

filter combined_condition [1..10] 
+2

Attention avec ($ x) par opposition à ($ x), comme si vous activez Template Haskell pour une autre raison, le $ x apparaîtra soudainement comme une épissure . –

+8

Vous avez découvert la fonction 'all':' filter (toutes les conditions) [1..10] ' –

+1

Il y a aussi la fonction any, selon la façon dont vous voulez combiner les prédicats: any p = ou. carte p; tous p = et. carte p; –

2

Si vous avez une liste de fonctions de filtrage de type a -> Bool et vouloir les combiner en une fonction de filtrage concise du même type, nous pouvons écrire des fonctions à faire juste. Laquelle des deux fonctions ci-dessous que vous utiliserez dépendra du comportement du filtre dont vous avez besoin.

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 retournera vrai si l'une des fonctions de filtrage return true et false si toutes les fonctions de filtre return false. allfilt retournera vrai si toutes les fonctions de filtre renvoient vrai et false si l'une des fonctions de filtre retourne false. Notez que vous ne pouvez pas réduire l'une ou l'autre fonction car les références à fns sur le RHS sont dans des fonctions anonymes.

utiliser comme ceci:

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"