その時凡人が動いた

凡人が凡人らしからぬことをするために日々奮闘するブログ

Swiftで関数のカリー化(currying)入門

f:id:alex23drum:20140727225644p:plain

今回はSwiftで関数のカリー化を実現する方法を紹介します!

そもそもカリー化って何?

カリー化とは、

複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること

引用元 : Wikipedia

です。ちょっとわかりづらいですかね?^^;

要は、aとbを引数にとってcを返すような関数fがあったとします。

\[ f(a, b) = c \]

このとき、引数にbをとってcを返す関数g(b)と、引数aをとって関数gを返す関数F(a)を定義すると、 この関数Fは 関数fをカリー化した関数 と言います。

\[ F(a) = g \] \[ g(b) = c \]

すなわち、ざっくり言うとカリー化とは 複数の引数を1つに減らす ことを指します。

Swiftで関数をカリー化してみる

では、早速関数をカリー化していきましょう! 例えば、「2つの整数を足し合わせる関数」を作るとします。普通に書いたら次のように書けるでしょう。

func addTwoNumbers(x: Int, y: Int) -> Int {
  return x + y
}

これをカリー化するためには、まず上の例でいう関数Fはどのようになるでしょうか? 関数Fとはwikipediaの説明によると、 引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数 であり、ここでいう 「もとの関数の残りの引数を取り結果を返す関数」 が関数gにあたります。 すなわち、関数Fをadd(x)、関数gをaddToX(y)とおくと、add(x)は以下のように表せます。

func add(x: Int) -> (Int -> Int) {
  func addToX(y :Int) -> Int {
    return x + y
  }
  return addToX
}

おめでとうございます。この関数add(x)こそが、addTwoNumbersの カリー化関数 (curried function)です!!さらに、addToXをいちいち定義してから返さないで、無名関数としてreturnしてやると、次のように書けます。

func add(x: Int) -> (Int -> Int) {
  return { (y: Int) -> Int in x + y }
}

確認してみると

let add7 = add(7)
add7(3)   // 10
add7(5)   // 12

このようになっていることが分かります。

そもそもカリー化なんかして何が嬉しいの??

では、関数をカリー化する利点はなんでしょうか?それは、 カリー化された関数によって、簡単に別の機能を持つ関数を作れる ということです。上の関数add(x)の例でいくと、引数に7を足す関数、引数に10を足す関数、引数に100を足す関数...が簡単に量産出来てしまいます。(処理が簡単なのであまり利点を感じないかもしれませんが、、、)

let add7 = add(7)
let add10 = add(10)
let add100 = add(100)

[注意!]部分適用と意味を混合しないこと!

よく「カリー化」と「部分適用」の意味を混合して使ってしまう例があります。 しかし、これらは似ているようで違う意味を持ち、実は「カリー化」は「部分適用」の一部なのです。

最初に私は「カリー化とは 複数の引数を1つに減らす こと」と述べましたが、それに対し、部分適用は 「引数を減らすこと」 です。すなわち、

func addThreeNumbers(x: Int, y: Int, z: Int) -> Int {
  return x + y + z
}

このような関数を、

func add(x: Int) -> ((Int, Int) -> Int) {
  return { (y: Int, z: Int) in x + y + z }
}

このように書き換えることを 部分適用 と言います。 これらの違いや、使い道に関しては、こちらのスライドに分かりやすくまとまっていますので是非ご一読ください^^

[備考]公式ドキュメントでは、、

Appleの公式ドキュメント "The Swift Programming Language" では、カリー化関数を以下のように書けると記されています。

func addTwoNumbers(a: Int)(b: Int) -> Int {
    return a + b
}
addTwoNumbers(4)(5) // Returns 9

便利そうですね。しかし、実際にこの記述で実行しようとするとエラーが出てしまい、以下のように記述しなければなりません。

func addTwoNumbers(a: Int)(b: Int) -> Int {
    return a + b
}
addTwoNumbers(4)(b: 5) // Returns 9

二つ目の引数のみbというラベルが必要になります。 お気をつけ下さい!!

お疲れさまでした!