多くの関数が pattern に従って定義されているのを見てきました(f .) . g
。例えば:
countWhere = (length .) . filter
duplicate = (concat .) . replicate
concatMap = (concat .) . map
これは何を意味するのでしょうか?
多くの関数が pattern に従って定義されているのを見てきました(f .) . g
。例えば:
countWhere = (length .) . filter
duplicate = (concat .) . replicate
concatMap = (concat .) . map
これは何を意味するのでしょうか?
ドット演算子 (すなわち(.)
) は関数合成演算子です。次のように定義されています。
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)
ご覧のとおり、型の関数と型b -> c
の別の関数を取り、型a -> b
の関数を返しますa -> c
(つまり、最初の関数を 2 番目の関数の結果に適用します)。
関数合成演算子は非常に便利です。ある関数の出力を別の関数の入力にパイプすることができます。たとえば、次のように Haskell でtacプログラムを作成できます。
main = interact (\x -> unlines (reverse (lines x)))
あまり読めません。ただし、関数合成を使用すると、次のように記述できます。
main = interact (unlines . reverse . lines)
ご覧のとおり、関数合成は非常に便利ですが、どこでも使用できるわけではありません。たとえば、関数合成filter
を使用して の出力をパイプすることはできません。length
countWhere = length . filter -- this is not allowed
これが許可されない理由filter
は、タイプが であるためです(a -> Bool) -> [a] -> [a]
。それを と比較すると、それはタイプであり、タイプでa -> b
あることがわかります。Haskell は型(つまり) であることを想定しているため、これにより型の不一致が発生します。ただし、実際にはタイプです。a
(a -> Bool)
b
[a] -> [a]
length
b -> c
([a] -> [a]) -> c
[a] -> Int
解決策は非常に簡単です。
countWhere f = length . filter f
ただし、余分なぶら下がりが気に入らない人もいf
ます。彼らは次のようcountWhere
に無意味なスタイルで書くことを好みます:
countWhere = (length .) . filter
彼らはどうやってこれを手に入れますか?検討:
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)
それはきれいではありませんが、それは仕事を成し遂げます。2 つの関数を指定すると、独自の関数合成演算子を作成することもできます。
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:
(.:) = (.) . (.)
同様に、次のようになります。
(.::) = (.) . (.) . (.)
(.:::) = (.) . (.) . (.) . (.)
ご覧のとおり(.:)
、(.::)
と(.:::)
は単に のべき乗です(.)
(つまり、の反復関数です(.)
)。数学の数の場合:
x ^ 0 = 1
x ^ n = x * x ^ (n - 1)
数学の関数についても同様です。
f .^ 0 = id
f .^ n = f . (f .^ (n - 1))
その場合f
:(.)
(.) .^ 1 = (.)
(.) .^ 2 = (.:)
(.) .^ 3 = (.::)
(.) .^ 4 = (.:::)
これで、この記事は終わりに近づきます。最後の課題として、ポイントフリー スタイルで次の関数を書きましょう。
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
これをさらに次のように単純化できます。
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
。
mf = compose map filter
はい、それは少し醜いですが、それはまた、本当に素晴らしい、気が遠くなるようなコンセプトでもあります. フォームの任意の関数を\x y z -> f x (g y z)
asで記述できるようにcompose f g
なりました。これは非常に優れています。
これは好みの問題ですが、私はそのようなスタイルが不快だと思います. 最初にそれが何を意味するのかを説明し、次に私が好む別の方法を提案します。
あなたはそれを知っている必要が(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
おり、おそらく他の場所でも定義されています。)