倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

Closure クラスの API を使ってみる (5) : カリー化(部分適用?) curry(), rcurry(), ncurry()

今回はカリー化、というか部分適用を行うメソッドを見ていきます。 クロージャのカリー化(部分適用?)とは、あるクロージャに対して予め引数の一部を固定したクロージャを生成する操作です。 拙者は関数型プログラミング初心者なので、カリー化と部分適用の厳密な違いがイマイチ分かりませんが、『プログラミングClojure』によると、すべての引数が固定された場合に

  • 定数を返すのがカリー化
  • 定数を返すクロージャ(関数)を返すのが部分適用

だそうです。 この意味では Closure に行えるのは部分適用のようですが、まぁ「カリー化」と「部分適用」という2つの言葉はほとんど同じ意味で使われるようだし、Closure クラスに定義されているメソッド名に curry とついてもいるので、この記事ではカリー化と呼ぶことにします。

参考

カリー化を行うメソッド


Closure クラスに定義されているカリー化のメソッドは以下の通り:

    Closure curry(Object argument)
    Closure curry(Object... arguments)

    Closure rcurry(Object argument)
    Closure rcurry(Object... arguments)

    Closure ncurry(int n, Object argument)
    Closure ncurry(int n, Object... arguments)
  • curry() は左から引数を固定するカリー化
  • rcurry() は右(right)から引数を固定するカリー化
  • ncurry() は場所を指定して引数を固定するカリー化

を行います。

簡単なサンプル・コード


では、簡単なサンプル・コードを見てましょう。 このサンプルでは、java.util.Random クラスの nextInt() メソッドを使って0以上1000未満の整数乱数を生成しようとしています。 通常は nextInt() を呼び出すたびに範囲を指定する1000を引数として渡さなければいけませんが、カリー化をすると引数なしのメソッド呼び出しにすることができます:

def random = new Random(1L)

def r = random.&nextInt    // Random#nextInt() メソッドをクロージャとして取得

def r1000 = r.curry(1000)    // カリー化
10.times{
    println r1000()    // 引数なしの呼び出し。 nextInt(1000) と同じ
}

Random オブジェクトが状態を持ってるので、引数全部固定してるのに呼び出し毎に返す値が異なってしまうという、反 FP コード(笑) でも数学的には定まった乱数返してるので OK な気も。

もう少し複雑なサンプル


上記のサンプルでは curry(Object) というメソッドしか使っていなかったので、他のメソッドも使用したサンプルを書いてみましょう:

def f = { a, b, c, d, e -> 'a'*a + 'b'*b + 'c'*c + 'd'*d + 'e'*e }

// curry() メソッド
def f_a = f.curry(1)
assert f_a(0, 0, 0, 0) == 'a'    // f(1, 0, 0, 0, 0) と同じ

def f_ab = f.curry(1, 2)
assert f_ab(0, 0, 0) == 'abb'    // f(1, 2, 0, 0, 0) と同じ

// rcurry() メソッド
def f_e = f.rcurry(1)
assert f_e(0, 0, 0, 0) == 'e'    // f(0, 0, 0, 0, 1) と同じ

def f_de = f.rcurry(1, 2)
assert f_de(0, 0, 0) == 'dee'    // f(0, 0, 0, 1, 2) と同じ

// ncurry() メソッド
def f_c = f.ncurry(2, 1)
assert f_c(0, 0, 0, 0) == 'c'    // f(0, 0, 1, 0, 0) と同じ

def f_cd = f.ncurry(2, 1, 2)
assert f_cd(0, 0, 0) == 'cdd'    // f(0, 0, 1, 2, 0) と同じ

ちょっとサンプルに持ってきたクロージャが分かりにくかったかも知れませんが、カリー化の挙動は簡単だと思います。 挙動がちょっと迷うのは rcurry(Object...) の引数割り当ての順番かと思いますが、順番はそのままで渡されるようでござる。

プログラミングGROOVY

プログラミングGROOVY