Python でカリー化を実装する

カリー化は複数の引数を取る関数を、1 つの引数を取る関数のチェーンに変換する手法です。部分適用と組み合わせることで柔軟な関数合成が可能になります。

カリー化とは

# 通常の関数
def add(a, b, c):
    return a + b + c

add(1, 2, 3)  # 6

# カリー化された関数
def add_curried(a):
    def f(b):
        def g(c):
            return a + b + c
        return g
    return f

add_curried(1)(2)(3)  # 6

汎用的なカリー化関数

from functools import wraps
import inspect

def curry(func):
    @wraps(func)
    def curried(*args, **kwargs):
        sig = inspect.signature(func)
        try:
            sig.bind(*args, **kwargs)
            return func(*args, **kwargs)
        except TypeError:
            return lambda *more_args, **more_kwargs: curried(
                *args, *more_args, **{**kwargs, **more_kwargs}
            )
    return curried

@curry
def add(a, b, c):
    return a + b + c

print(add(1)(2)(3))       # 6
print(add(1, 2)(3))       # 6
print(add(1)(2, 3))       # 6

実用例:設定の部分適用

@curry
def format_message(template, name, value):
    return template.format(name=name, value=value)

# テンプレートを固定
greeting = format_message("Hello, {name}! Your score is {value}.")
print(greeting("Alice")(100))
# Hello, Alice! Your score is 100.

部分適用との違い

カリー化

複数引数関数を 1 引数関数のチェーンに変換。f(a, b, c) → f(a)(b)©

部分適用

一部の引数を固定した新しい関数を作る。f(a, _, c) → g(b)

functools.partial との組み合わせ

from functools import partial

@curry
def power(base, exponent, modulo=None):
    if modulo:
        return pow(base, exponent, modulo)
    return base ** exponent

square = power(exponent=2)  # カリー化で base を待つ
print(square(5))  # 25

カリー化は関数型プログラミングの基盤であり、ポイントフリースタイルや関数合成と相性が良いです。