Groovyは部分的なアプリケーションを「カリー化」と呼びますか?


15

Groovyには、「カリー化」と呼ばれる概念があります。Wikiの例を次に示します。

def divide = { a, b -> a / b }

def halver = divide.rcurry(2)

assert halver(8) == 4

ここで何が起こっているかについての私の理解は、 divide値2にバインドされているということです。これは、部分的なアプリケーションの形式のようです。

通常、カリー化という用語は、一連の引数をとる関数を、1つの引数のみを取り、別の関数を返す関数に変換することを意味するために使用されます。たとえばcurry、Haskell の関数のタイプは次のとおりです。

curry :: ((a, b) -> c) -> (a -> (b -> c))

Haskellのを使用していない人のためにabそしてc、すべての一般的なパラメータです。 curryは、2つの引数を持つ関数を受け取りa、からbまでの関数を受け取って返す関数を返しますc。これを明確にするために、型に余分な括弧を追加しました。

グルーヴィーな例で何が起こっているのか誤解しているのですか、それとも単に部分的なアプリケーションの名前を間違っているのですか?または、実際には両方を行います。つまりdivide、カリー化された関数に変換し、2この新しい関数に部分的に適用します。


1
Haskellの話さないもののためにmsmvps.com/blogs/jon_skeet/archive/2012/01/30/...
JKを。

回答:


14

Groovyの実装 curry、舞台裏であっても実際にはどの時点でもカレーしません。これは、部分的なアプリケーションと本質的に同じです。

curryrcurryおよびncurry方法は返すCurriedClosureオブジェクトをバインドされた引数を保持しています。またgetUncurriedArguments、バインドされた引数とともに渡された引数の構成を返すメソッド(名前は間違っています-引数ではなく関数です)があります。

クロージャが呼び出されると、最終的にinvokeMethodメソッドをMetaClassImpl呼び出し、呼び出し元のオブジェクトがのインスタンスであるかどうかを明示的に確認しますCurriedClosure。その場合、前述の方法getUncurriedArgumentsを使用して、適用する引数の完全な配列を作成します。

if (objectClass == CurriedClosure.class) {
    // ...
    final Object[] curriedArguments = cc.getUncurriedArguments(arguments);
    // [Ed: Yes, you read that right, curried = uncurried. :) ]
    // ...
    return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments);
}

上記の混乱したやや一貫性のない命名法に基づいて、私はこれを書いた人は誰でも概念的な理解を持っていると思うが、多分少し急ぎ、おそらく多くの賢い人々のようにカリー化を部分適用と混同した。少し不幸な場合、これは理解できます(ポールキングの答えを参照)。後方互換性を損なうことなくこれを修正することは困難です。

私が提案した解決策の1つcurry、引数が渡されないときに実際にカリー化するようにメソッドをオーバーロードし、新しいpartial関数を優先して引数を使用してメソッドを呼び出すことを非推奨にすることです。これは少し奇妙思えるかもしれませんが、後方互換性を最大化するでしょう(引数なしで部分的なアプリケーションを使用する理由がないためです)。 named curryは何か違うもので、紛らわしいほど似ています。

curry言うまでもなく、呼び出しの結果は実際のカレーとはまったく異なります。関数が本当にカリー化されている場合、次のように書くことができます。

def add = { x, y -> x + y }
def addCurried = add.curry()   // should work like { x -> { y -> x + y } }
def add1 = addCurried(1)       // should work like { y -> 1 + y }
assert add1(1) == 2 

…そしてのように動作するaddCurriedはずなので、動作し{ x -> { y -> x + y } }ます。代わりに、実行時例外がスローされ、内部で少し死にます。


1
私は、引数を持つ関数のrcurryとncurryは> 2が、これは本当にカリー化だけではなく、部分的なアプリケーションであることを証明していると思う
JK。

@jk実際、これは引数== 2の関数で実証可能です。最後に説明します。:)
ジョーダングレー

3
@matcauthon厳密に言えば、カリー化の「目的」は、多くの引数を持つ関数を、それぞれ1つの引数を持つネストされた関数チェーンに変換することです。あなたが求めているのは、カリー化を使用したい実用的な理由だと思います。これは、LISPやHaskellなどよりもGroovyで正当化するのが少し難しいです。重要なのは、おそらくほとんどの場合に使用したいのは、カレーではなく部分的なアプリケーションです。
ジョーダングレー

4
+1and you die a little inside
トーマスエディング

1
うわー、あなたの答えを読んだ後のカレーの方がずっとよくわかります:+1とありがとう!質問に固有のことですが、「部分適用と混成カレー」と言ったことが気に入っています。
グレンペターソン

3

3つ以上の引数を持つ関数を検討するとき、グルービーカレーは実際には部分的なアプリケーションであることは明らかだと思います。考える

f :: (a,b,c) -> d

そのカレー形式は

fcurried :: a -> b -> c -> d

ただし、groovyのカレーは(1つの引数xで呼び出されると仮定して)同等のものを返します。

fgroovy :: (b,c) -> d 

aの値をxに固定してfを呼び出します

つまり、groovyのカレーはN-1個の引数を持つ関数を返すことができますが、カリー化された関数は1つの引数しか持っていないため、groovyはカレーでカリーできません


2

Groovyは、カリーメソッドの命名法を、部分アプリケーションにも同様の命名法を使用する他の多くの非純粋なFP言語から借用しました。Groovyに含めるためのいくつかの「実際の」カリー化実装が提案されています。それらについて読み始めるための良いスレッドはこちらです:

http://groovy.markmail.org/thread/c4ycxdzm3ack6xxb

既存の機能は何らかの形のままであり、新しいメソッドなどに名前を付けるときに呼び出しを行う際には、後方互換性が考慮されます。したがって、この段階では、新しい/古いメソッドの最終的な命名方法を言うことはできませんである。おそらくネーミングの妥協案ですが、これから説明します。

ほとんどのオブジェクト指向プログラマーにとって、2つの用語(カリー化と部分適用)の区別はほぼ間違いなくアカデミックです。ただし、一度慣れると(コードを保守する人は誰でもこのスタイルのコーディングを読むように訓練されます)、ポイントフリーまたは暗黙のスタイルのプログラミング(「実際の」カリー化サポート)により、特定の種類のアルゴリズムをよりコンパクトに表現できます場合によってはよりエレガントに。ここには明らかに「見る人の目にある美しさ」がありますが、両方のスタイルをサポートする能力を持つことは、Groovyの性質(OO / FP、静的/動的、クラス/スクリプトなど)と一致しています。


1

IBMで見つかったこの定義を考えると:

カレーという用語は、部分関数の概念を開発した数学者、Haskell Curryから取られています。カリー化とは、多数の引数を取る関数に複数の引数を取り、残りの引数を取り、結果を返す新しい関数を作成することです。

halverこれは、新しい(カリー化された)関数(またはクロージャー)であり、パラメーターを1つだけ取ります。呼び出しhalver(10)は5になります。

そのため、n-1個の引数を持つ関数のn個の引数を持つ関数を変換します。haskellの例でも、カレーが何をするかと同じことが言えます。


4
IBMの定義は正しくありません。彼らがカリー化と定義するのは、実際には部分的な関数アプリケーションであり、関数の引数をバインド(修正)して、より小さなアリティを持つ関数を作成します。カリー化は、複数の引数を取る関数を、それぞれが1つの引数を取る関数のチェーンに変換します。
ジョーダングレー

1
ウィキペディアの定義はIBMのものと同じです。数学およびコンピューターサイエンスでは、カリー化は、複数の引数(またはnタプルの引数)をとる方法で呼び出す関数を変換する手法です。それぞれが単一の引数を持つ関数のチェーン(部分アプリケーション)。 Groovyは、(rcurry1つの引数を取る)関数を持つ(2つの引数を持つ)関数を(1つの引数のみを持つ)関数に変換します。結果の関数を取得するために、基本関数への引数を使用してカレー関数をチェーンしました。
matcauthon

3
いやウィキペディアの定義が異なっている-あなたはカリー化関数を呼び出すとき、部分的なアプリケーションです-あなたはそれを定義するときにグルーヴィーが何をするかではないいる
JK。

6
@jkは正しいです。ウィキペディアの説明をもう一度読んでください。返されるのは、引数を持つ1つの関数ではなく、それぞれ1つの引数を持つ関数のチェーンであることがわかりn - 1ます。私の答えの最後にある例を参照してください。行われている区別の詳細については、記事の後半を参照してください。en.wikipedia.org/wiki/...
ヨルダングレー

4
とても重要です、私を信じてください。繰り返しますが、私の答えの最後にあるコードは、正しい実装がどのように機能するかを示しています。一つには、それは議論を必要としません。現在の実装は本当に例と命名されるべきpartialです。
ジョーダングレー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.