LoginSignup
44
27

More than 5 years have passed since last update.

Haskellの関数合成、または (.) 関数

Posted at

Haskell の関数合成について書いてある記事が意外と少なかったので、私が詰まづいたところをメモしておきます。

ここでは、部分適用の知識があることを前提にしたいと思います。
また、ここでは数学や圏論の話は極力抜きで。私自身が苦手なので。

ポイントフリースタイル

まず初めに、

f x = g x

という式があったとき、このxを「ポイント」と呼びます。
そしてこの式は、

f = g

と書くことができます。
この書き方を「ポイントフリースタイル」と呼びます。

関数合成

複数の関数を結合したい場合、

f x = g $ h x

といった書き方をすると思います。
これはポイントがカッコ内にあるため、このままではポイントフリースタイルにすることはできません。

しかし、(.)という関数を使うと上の式を、

f x = (g . h) x

と書くことができます。
この「二つの関数を繋げて一つの関数にする」ことを、「関数合成」と呼びます。

f = g . h

ポイントフリースタイルにすることができました。

二引数関数

Haskell には正確には二引数関数は存在しませんが、ここでは便宜上、二つの引数を取る関数をそう呼ぶことにします。

たとえば(++)関数の型は、

(++) :: [a] -> [a] -> [a]

と、見た目上は二引数関数になっています。

では、例として二つの文字列を取り、さらに反転させる関数を考えてみましょう。

f x y = reverse $ (++) x y

単純に考えると、次のようになると思います。

-- f :: String -> String -> String
f = reverse . (++)

しかし、この式を実行すると

main.hs:1:19:
    Couldn't match type `[a1] -> [a1]' with `[a0]'
    Expected type: [a1] -> [a0]
      Actual type: [a1] -> [a1] -> [a1]
    In the second argument of `(.)', namely `(++)'
    In the expression: reverse . (++)
    In an equation for `f': f = reverse . (++)

というエラーが出てしまいます。

これは、要約すると「reverse . (++)に引数を適用しても、返ってくるのが[a1] -> [a1]という型(の関数)であるため、reverseの引数の型[a0]と一致しない」という内容です。

では、二引数関数を関数合成したい場合、どうすれば良いのでしょうか。

(.)関数とは

そもそも(.)関数とは何でしょうか。
型は次のように定義されています。

(.) :: (b -> c) -> (a -> b) -> a -> c

ここから推測すると、(.)の正体は
1. 第二引数::a -> bに第三引数:: aを適用させる
2. 第一引数::b -> cに上の値:: bを適用させる
関数だということになります。

ところで、先程出てきた

f = reverse . (++)

という式は、

f x y = ((.) reverse (++)) x y

と書くことができます。この式を変形させると、

f x y = ((.) reverse (++)) x y
f   y = (reverse ((++) x)) y

となり、関数がカッコの中に入ってしまうためエラーが出ていました。
(.)は、三番目の引数に二番目の引数を適用させる関数ですから、この時点で

f   y = ((.) reverse ((++) x)) y

となっていれば良いのです。
つまり、式を成立させるには

f = ((.) (reverse)) . (++)
f = (reverse .) . (++) -- 上式と同じ

と書けば良いということになります。

応用

前述の関数を使って、いくつかのパターンを考えてみましょう。

三引数関数

f x y z = g $ h x y z

という三引数関数を、ポイントフリースタイルにしてみましょう。

f x y z = g (((h x) y) z)
f x y   = (.) g ((h x) y)
f x     = (.) ((.) g) (h x)
f       = (.) ((.) ((.) g)) h
f       = (((g .) .) . h -- 上式と同じ

n引数関数はパターン見えてきました。

引数あり . 引数あり

f x y = g x $ h y

という関数を考えてみます。

f x y = g x (h y)
f x y = flip g (h y) x
f x y = ((.) (flip g) h) y x
f     = flip ((.) (flip g) h)
f     = flip $ flip g . h -- 上式と同じ

flip関数を併用すると、ポイントフリーにできる関数の幅も広がりそうです。

とりあえず今回はここまで。

44
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
27