カリー化とは何かについてのあなたの基本的な理解は正しいです。具体的には、たとえば次のように、引数をタプルとして受け取る関数を変換することです。
add :: (Int, Int) -> Int
add (x, y) = x + y
一度に 1 つずつ引数を取る関数に変換します。
add' :: Int -> Int -> Int
add' x y = x + y
このスキームにより、現在カリー化された関数を部分適用することができます。つまり、まだすべての引数ではなく一部の引数を適用します。たとえば、
succ :: Int -> Int
succ = add' 1
ここでadd'
、最初の引数に適用し、残りの引数を期待する関数を生成します。
逆変換は uncurrying と呼ばれ、引数を「1 つずつ」受け取る関数を、その引数をタプルとして「一度に」受け取る関数に変換します。
どちらの変換も、高階関数のファミリによってキャプチャできます。つまり、バイナリ関数には
curry :: ((a, b) -> c) -> (a -> b -> c)
curry f = \x y -> f (x, y)
uncurry :: (a -> b -> c) -> ((a, b) -> c)
uncurry f = \(x, y) -> f x y
三項関数には
curry3 :: ((a, b, c) -> d) -> (a -> b -> c -> d)
curry3 f = \x y z -> f (x, y, z)
uncurry3 :: (a -> b -> c -> d) -> ((a, b, c) -> d)
uncurry3 f = \(x, y, z) -> f x y z
などなど。
それでは、あなたの例を見てみましょう:
2 * (\x y -> x * y) 2 3
ここでは、2 つの引数とを乗算2
する関数を適用した結果をリテラルに乗算しています。ご覧のとおり、この関数はすでに「1 つずつ」引数を取ります。したがって、それはすでにカレーです。したがって、この式のカリー化されたバージョンを書くように求められた場合、チュートリアルで何を意味するのかは私には理解できません. できることは、乗算関数に「すべて一度に」引数をとらせることで、カリー化されていないバージョンを作成することです。次に、取得します(\x y -> x * y)
x
y
(\(x, y) -> x * y)
2 * (\(x, y) -> x * y) (2, 3)
(\(x, y) -> x * y)
のように書くことができることに注意してuncurry (*)
ください。
2 * uncurry (*) (2, 3)
の最初のアプリケーション (または実際にはアプリケーション、複数 ;-)) もアンカリー化すると、次の結果が(*)
得られます。
uncurry (*) (2, uncurry (*) (2, 3))
これがチュートリアルの演習の背後にある意図であったかどうかは疑問ですが、これがカリー化とアンカリー化についての洞察を提供してくれることを願っています.