43

私はパターン(f .) . gに従って定義されている多くの機能を見てきました。例:(f。)とは何ですか?ハスケルでの平均は?

countWhere = (length .) . filter 
duplicate = (concat .) . replicate 
concatMap = (concat .) . map 

これはどういう意味ですか?

+4

(F。)。gは元の著者のコードについてうまく偽装された意見の一部でもあります。 – Marton

+1

私はそのことがどういう意味なのかよく分かりません。 –

+0

これは、著者が賢明になり、読めないコードを書くことになったことを意味します。 ;) – tibbe

答えて

82

ドット演算子((.))はfunction composition演算子です。次のように定義されます

infixr 9 . 
(.) :: (b -> c) -> (a -> b) -> a -> c 
f . g = \x -> f (g x) 

あなたが見ることができるように、最初に2つ目の関数の結果を適用する(すなわちタイプb -> cとタイプa -> bの別の関数の機能を取り、タイプa -> cの機能を返します関数)。

関数合成演算子は非常に便利です。ある関数の出力を別の関数の入力にパイプすることができます。

main = interact (\x -> unlines (reverse (lines x))) 

ない非常に読みやすいを次のようにたとえば、あなたはHaskellでtacプログラムを書くことができます。次のようにあなたはそれを書くことができますが、機能性組成物を用い:

main = interact (unlines . reverse . lines) 

あなたは関数合成を見ることができるようにすることは非常に便利ですが、あなたはどこにでもそれを使用することはできません。例えば、関数合成を使用してlengthにあなたがすることができない、パイプfilterの出力を:filterはタイプ(a -> Bool) -> [a] -> [a]であるため

countWhere = length . filter -- this is not allowed 

これが許可されていない理由があります。 a -> bと比較すると、aはタイプ(a -> Bool)であり、bはタイプ[a] -> [a]であることがわかります。 Haskellではlengthのタイプがb -> c(つまり([a] -> [a]) -> c)であると予想しているため、タイプが一致しません。しかし実際にはタイプ[a] -> Intです。

ソリューションは非常に簡単です:

countWhere f = length . filter f 

しかし、一部の人々は、余分なダングリングfが好きではありません。

countWhere = (length .) . filter 

を、彼らがこれを取得するにはどうすればよい:彼らは、次のようにpointfreeスタイルでcountWhereを書くことを好みますか?考えてみましょう:

countWhere f xs = length (filter f xs) 

-- But `f x y` is `(f x) y`. Hence: 

countWhere f xs = length ((filter f) xs) 

-- But `\x -> f (g x)` is `f . g`. Hence: 

countWhere f = length . (filter f) 

-- But `f . g` is `(f .) g`. Hence: 

countWhere f = (length .) (filter f) 

-- But `\x -> f (g x)` is `f . g`. Hence: 

countWhere = (length .) . filter 

あなたが(f .) . gを見ることができるように、単に\x y -> f (g x y)です。この概念は、実際には反復することができます。

f . g    --> \x -> f (g x) 
(f .) . g   --> \x y -> f (g x y) 
((f .) .) . g  --> \x y z -> f (g x y z) 
(((f .) .) .) . g --> \w x y z -> f (g w x y z) 

それはかなりありませんが、それは仕事を取得します。二つの機能を考えると、あなたはまた、独自の関数合成演算子を書き込むことができます。

f .: g = (f .) . g 
f .:: g = ((f .) .) . g 
f .::: g = (((f .) .) .) . g 

代わりに次のようにcountWhereを書くことができ(.:)オペレータの使用:興味深いことに

countWhere = length .: filter 

をあなたのようにポイント自由なスタイルで(.:)を書くことができてもうまく:

f .: g = (f .) . g 

-- But `f . g` is `(.) f g`. Hence: 

f .: g = (.) (f .) g 

-- But `\x -> f x` is `f`. Hence: 

(f .:) = (.) (f .) 

-- But `(f .)` is `((.) f)`. Hence: 

(f .:) = (.) ((.) f) 

-- But `\x -> f (g x)` is `f . g`. Hence: 

(.:) = (.) . (.) 

同様に、我々が得る:

(.::) = (.) . (.) . (.) 
(.:::) = (.) . (.) . (.) . (.) 

あなたが(.:)(.::)(.:::)(.)だけの力ある見ることができるように(すなわち、それらはiterated functions(.)です)。数学の番号について:数学関数についても同様

x^0 = 1 
x^n = x * x^(n - 1) 

f .^ 0 = id 
f .^ n = f . (f .^ (n - 1)) 

f場合は、(.)です:この記事の最後に近い私たちをもたらします

(.) .^ 1 = (.) 
(.) .^ 2 = (.:) 
(.) .^ 3 = (.::) 
(.) .^ 4 = (.:::) 

mf = compose map filter 
:あなたは今のように mfを書くことができ

compose f g = (. f) . (.) . g 

compose f g = ((. f) . (.)) . g 

compose f g = (.) ((. f) . (.)) g 

compose f = (.) ((. f) . (.)) 

compose f = (.) ((. (.)) (. f)) 

compose f = ((.) . (. (.))) (. f) 

compose f = ((.) . (. (.))) (flip (.) f) 

compose f = ((.) . (. (.))) ((flip (.)) f) 

compose = ((.) . (. (.))) . (flip (.)) 

composeを使用して次のように

mf a b c = filter a (map b c) 

mf a b c = filter a ((map b) c) 

mf a b = filter a . (map b) 

mf a b = (filter a .) (map b) 

mf a = (filter a .) . map 

mf a = (. map) (filter a .) 

mf a = (. map) ((filter a) .) 

mf a = (. map) ((.) (filter a)) 

mf a = ((. map) . (.)) (filter a) 

mf = ((. map) . (.)) . filter 

mf = (. map) . (.) . filter 

私たちは、これをさらに簡素化することができます。最終的な課題についてのはpointfreeスタイルで次の関数を書いてみましょう

はい、それは少し醜いですが、それはまた、本当に素晴らしい心を動揺させる概念です。 \x y z -> f x (g y z)という形式の関数をcompose f gと書くことができます。これはきれいです。

+2

'(。)^ i'という形式の式は型が正しくないので、実際には有効ではありません。 –

+1

真。しかし、私は「関数の場合と同様に」と書いています。これは数学的な説明なので、数値の代わりに関数には '^'を使うのがいいと思います。それにもかかわらず、私は演算子を '。^ 'に変更して2つを区別します。 –

+0

私は数学で '(。)^ i'も見るのに驚くでしょう。おそらく、そのようなものの正式な枠組みは、依存型理論に存在します。面白そうですね。 –

11

これは味の問題ですが、私はそのようなスタイルが不愉快であると感じます。最初に私はそれが意味するものを説明し、次に私が好む選択肢を提案します。

任意の演算子の場合、(f . g) x = f (g x)(f ?) x = f ? xを知る必要があります?。このことから、我々はその

countWhere p = ((length .) . filter) p 
       = (length .) (filter p) 
       = length . filter p 

ので

countWhere p xs = length (filter p xs) 

推測することができ、私はその後.:

(.:) :: (r -> z) -> (a -> b -> r) -> a -> b -> z 
(f .: g) x y = f (g x y) 

と呼ばれる機能countWhere = length .: filterを使用することを好みます。個人的に私はこれをはるかに明確にしています。

.:があまりにData.Compositionそしておそらく他の場所で定義されています。)

+0

'(。:) = fmap fmap fmap'として'(。:) 'を定義することもできます。あなたが任意のファンクタのために使用できるので、より一般的です。例えば、 '(* 2)。:Just [1..5]'を実行することができます。もちろん、 '(。:) ::(Functor f、Functor g)=>(a - > b) - > f(g a) - > f(g b)'の正しい型シグネチャを与える必要があります。 –

+6

@AaditMShahその場合、私は '<$$> = fmapのようなものを好むでしょう。 '(。:)は' '( - >)r'に特化しているので、' 'fmap''は' '(' 'r'')関数の中にあります。 – kqr

関連する問題