Pythonでカリー化する方法

今回は関数型プログラミングの中で頻繁に登場するカリー化というテクニックを紹介します。

カリー化はpartial関数のシンタックスシュガーであるためpartial関数について知っていれば難しくはありません。partialの復習も兼ねてカリー化と比較しながら紹介します。

partial関数のおさらい

partial関数は関数に部分適用できる機能を持っています。

例えば、引数を2つ受け取りその積を返す関数mul(a, b)があります。どんな数値も2倍にする関数doubled(b)が欲しい場合は、partial関数を使って以下のように作成できます。

doubled = partial(mul, 2) # 部分適用
  
print(doubled(2))
print(doubled(11))

【結果】

4
22

カリー化

カリー化は部分適用のシンタックスシュガーです。つまりpartial関数を簡潔に表現できるものです。ちなみにカリー化の”カリー”とは人名に由来するものです。

複数の高次関数をチェーン化する時に役立ちます。

カリー化された関数は、結果を出力するために必要な引数を得られなかった場合、部分適用された関数を返します。

以下のようにcurryデコレータを使って関数を定義するだけでカリー化できます。

from toolz import curry
@curry
def mul(x, y):
    return x * y

curryクラスの初期化引数に渡してカリー化することも可能です。

mul = curry(mul)

カリー化した関数を以下のように使うことができます。

doubled = mul(2)
print(doubled(5))

print(mul(3)(2))

【結果】

10
6

mul関数は引数が2つ必要ですが、1つしか受け取っていません。そのため、xに2を部分適用した関数を返します。doubleは以下の関数と同等です。

def double(y):
    return 2 * y

カリー化されたmul関数は以下と同等の関数になります。

def mul(x):
    def mul_x(y):
        return x * y
    return mul_x

引数を1つ取る関数の入れ子構造になっていることがわかります。

上記の”mul(3)(2)”のようにmul関数の後に”(2)”をつけることでmul関数内のmul_x関数の引数に 2 を渡すことができます。

mapをカリー化

以下のようなリストの要素を全て2倍にしたリストを生成する処理があるとします。

def doubled(a):
    return a*2

nums = [1,2,3,4,5]
doubled = partial(map, doubled) # (1)部分適用
r = list(doubled(nums)) # r = [2, 4, 6, 8, 10]

上記コードの”(1)部分適用”とコメントしてある行は、次のようにmapをカリー化し、partialで部分適用していた箇所を、カリー化したmap関数で置き換えられます。

map = curry(map)
doubled = map(doubled)

このように部分適用可能なmapにすることで簡潔に書くことができます。

また、上記の例からカリー化はpartial関数のシンタックスシュガーであることが伝わると思います。

このケースに限ってはpartialの方が1行で書けるのでシンプルです。

ただし、partial関数で何度も部分適用するような関数がある場合は、カリー化した関数や、curried名前空間に切り替えた方が便利な時があります。

Curried名前空間

toolz名前空間にあるすべての関数は、toolz.curried名前空間でカリー化されています。そのため、インポート文を

from toolz import *

から、

from toolz.curried import *

のようにすることで、カリー化されたtoolzの関数を使うことができます。

map, filter, reduceのようなPython標準の高階関数をカリー化したものもあります。

具体的にどのような関数が利用できるかは、githubのページを参照してください。

参考サイト:PyToolz API Documentation

スポンサーリンク
スポンサーリンク