あるプログラマの日記

プログラマのメモ、出来事、考えたこと、勉強とかの雑記

部分適用関数、カリー化

部分適用関数

複数の引数をとる関数の引数の一部に値を指定し、残った引数を未適用にした状態の
部分適用関数を作成できる。

foo の3つの引数の内、真ん中の引数を未確定にして前後の引数を指定した
部分適用関数値を pa に代入。
pa は foo の真ん中の引数だけを指定する部分適用関数になる。
部分適用関数の未確定部分は、 _: 型 の形式で書く。

scala> def foo(a: Int, b: Int, c:Int) = a + b + c
foo: (a: Int, b: Int, c: Int)Int

scala> val pa = foo(4, _: Int, 6)
pa: Int => Int = <function1>

scala> pa(5)
res0: Int = 15

scala> foo(4, 5, 6)
res1: Int = 15

pa(5) は foo(4, 5, 6) を呼び出した場合と同じ結果を返す。

最初の引数の値に 7 を指定した部分適用関数

scala> val pb = foo(7, _: Int, _: Int)
pb: (Int, Int) => Int = <function2>

scala> pb(9, 10)
res2: Int = 26

scala> foo(7, 9, 10)
res3: Int = 26

部分適用関数とは直接関係ないが、3つの引数をとる元の foo を関数オブジェクトとして
変数に代入する場合は、引数リストをプレースホルダーのアンダースコア _ でまとめて指定できる。

scala> val bar = foo _
bar: (Int, Int, Int) => Int = <function3>

カリー化 (currying)

  • 1つの引数をとる複数の引数リストを持たせること
  • 言い換えると複数の引数をとる(1つの引数リストを持つ)関数を1つの引数をとる関数チェーンに変換すること

上の(3つの引数を取る1つの引数リストを持つ)関数 foo をカリー化した
(1つの引数を取る3つの引数リストを持つ)関数 foo3 を定義する。

scala> def foo3(a: Int)(b: Int)(c: Int) = a + b + c
foo3: (a: Int)(b: Int)(c: Int)Int

各引数リストの引数に、それぞれ引数を指定すると
3つの引数を取る1つの引数リストを持つ関数 foo を呼び出した時と同様に結果を返す。
結果は同じだが動作としては以下のようになる。

  1. 1つ目(左端)の引数リストから引数を適用して関数値を返す。
  2. 返された関数値は2つ目の引数リストから引数を適用して関数値を返す。
  3. さらに返された関数値は最後の引数リストから引数を適用して計算結果の値を返す。
scala> foo3(1)(2)(3)
res0: Int = 6

カリー化した関数に部分適用した関数を作成する。
1つ目の引数リストに引数を指定した部分適用関数

scala> val s1 = foo3(1) _
s1: Int => Int => Int = <function1>

scala> val s2 = s1(2)
s2: (Int) => Int = <function1>

scala> val s3 = s2(3)
s3: Int = 6


1つ目の引数リストの引数と2つ目の引数リストの引数を指定した部分適用関数

scala> val f2 = foo3(1)(2) _
f2: (Int) => Int = <function1>

scala> val f3 = f2(3)
f3: Int = 6

2つの整数と2つの整数を引数にとって整数を返す関数を引数に指定する。
3つ目の引数リストで渡される関数で処理した結果を返す。

scala> def foo4(a: Int)(b: Int)(f: (Int, Int) => Int) = f(a, b)
foo4: (a: Int)(b: Int)(f: (Int, Int) => Int)Int

1つ目の引数リストに整数 2 を指定した部分適用関数を作成する。
部分適用関数 ff1 へ2つ目の引数リストの引数に整数と3つ目の引数リストの引数に関数リテラルを渡す。
関数リテラルプレースホルダーを使用すると1つ目と2つ目の引数リストに指定した整数が適用される。

scala> val ff1 = foo4(2) _
ff1: (Int) => ((Int, Int) => Int) => Int = <function1>

scala> ff1(3) { _ + _ }
res1: Int = 5

scala> ff1(3) { _ * _ }
res2: Int = 6

関数オブジェクトを curried を使ってカリー化する。
curried は Function2〜22 トレイトのメソッド

scala> def foo(a: Int, b: Int, c:Int) = a + b + c
foo: (a: Int, b: Int, c: Int)Int

scala> val bar = foo _
bar: (Int, Int, Int) => Int = <function3>

scala> val foobar = bar.curried
foobar: (Int) => (Int) => (Int) => Int = <function1>

scala> foobar(3)(4)(5)
res0: Int = 12