2009-05-08 9 views
23

関数のフィルタクラスは条件(a - > Bool)をとり、フィルタリング時に適用します。フィルタ条件の結合方法

複数の条件があるときにフィルタを使用する最も良い方法は何ですか?

liftM2の代わりにアプリケーション機能 liftA2を使用しました。何らかの理由でliftM2が純粋なコード内でどのように動作していたか理解できなかったためです。

答えて

29

liftM2、あなたの述語関数に名前を付ける必要はありませんラムダを使用することができますコンビネータは、「より機能的」な方法でこれを行うためにリーダーモナドで使用することができます。輸入が重要であることを

import Control.Monad 
import Control.Monad.Reader 

-- .... 

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

は注意。 Control.Monad.Readerは、これをすべて動作させるMonad(e - >)インスタンスを提供します。

なぜなら、読者のモナドは、ある環境eではちょうど(e - >)であるからです。したがって、ブール・プレディケートは、その引数に対応する環境でブールを返す0個のモナド関数です。次に、liftM2を使用して、2つの述語を介して環境を配布することができます。

や型が出て作業する場合、単純な用語で、liftM2は、このようなビットを動作します:

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

また、あなたはこれらの簡単連鎖にできるようにしたい場合は、新しいコンビネータを定義し、することができます/または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

両方の例は、あなたが 'Control.Monad.Reader'をインポートしない場合でも、GHC 7.6.3で動作します:

はこのようにそれを使用してください。 – sjakobi

+2

私は 'liftM2'を(今日では)次のように置き換えることができます:' filter((&&)<$>奇数<*>(> 100))[1.200] '。どちらが同じですが、よりきれいです。 :)また、 'Control.Applicative'のみ必要で、完全なモナドはありません。 ...私はまだオペレータが2つ以上のブール関数のAND演算を可能にするのは不思議です... – Evi1M4chine

15

さて、あなたはHaskellでほしい(ただし限りのタイプが正しいとして)機能を組み合わせて、あなたもすなわち、

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

のは、あなたの条件がconditionsと呼ばれるリストに格納されているとしましょう。このリストのタイプは[a -> Bool]です。

は値 xにすべての条件を適用するには、 mapを使用することができます。

map ($ x) conditions 

これはxに各条件を適用し、ブールのリストを返します。そうでない場合は、すべての要素がTrueの場合はTrue、Falseを、単一のブールにこのリストを軽減するために、あなたはand機能を使用することができます。

and $ map ($ x) conditions 

は今、あなたはすべての条件を組み合わせた機能を持っています。この関数は、タイプ a -> Boolを持っているので、私たちは filterへの呼び出しで使用することができます

combined_condition x = and $ map ($ x) conditions 

::それに名前を挙げてみましょうあなたがタイプa -> Boolのフィルタリング機能のリストを持っている場合は

filter combined_condition [1..10] 
+2

($ x)とは対照的に($ x)、Template Haskellを何らかの理由で有効にしたように$ xは突然スプライスのように見えます。 –

+8

'all'関数を発見しました:' filter(all conditions)[1..10] ' –

+1

述語をどのように組み合わせるかによって、any関数もあります: 任意のp =または。マップp; all p =と。マップp; –

2

と同じ型の1つの簡潔なフィルタリング関数にそれらを結合したいと思うならば、ちょうど行うための関数を書くことができます。以下の2つの機能は、必要なフィルタの動作に依存します。

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フィルタ機能のすべてがfalseを返す場合、フィルタ機能のいずれかが trueとfalseを返す場合はtrueをを返します。 allfiltは、すべてのフィルタ関数がtrueを返す場合はtrueを返し、の場合はfalseを返します。 RHS上のfnsへの参照は匿名関数内にあるため、いずれの関数もη-reduceできないことに注意してください。

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"