Groovyには「カレー化」と呼ばれる概念があります。ここに彼らのウィキからの例があります:
_def divide = { a, b -> a / b }
_
def halver = divide.rcurry(2)
assert halver(8) == 4
ここで何が起こっているかについての私の理解は、divide
の右側の引数が値2にバインドされているということです。これは、部分的なアプリケーションの形のように見えます。
カリー化という用語は通常、一連の引数を取る関数を、1つの引数のみを受け取り、別の関数を返す関数に変換することを意味するために使用されます。たとえば、以下はHaskellのcurry
関数のタイプです。
curry :: ((a, b) -> c) -> (a -> (b -> c))
Haskellを使用したことがない人のためにa
、b
およびc
はすべてジェネリックパラメータです。 curry
は2つの引数を持つ関数を取り、a
を取り、b
からc
までの関数を返す関数を返します。これをより明確にするために、型にブラケットのペアを追加しました。
グルーヴィーな例で何が起こっているのかを誤解しましたか、それとも単に部分的なアプリケーションの名前を間違っているだけですか?または実際には両方を実行します。つまり、divide
をカレー関数に変換し、この新しい関数に_2
_を部分的に適用します。
Groovyのcurry
の実装は、舞台裏でも、実際にはカレーしません。これは本質的に部分適用と同じです。
curry
、rcurry
、および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);
}
上記の紛らわしくて多少一貫性のない命名法に基づいて、私はこれを書いた人は誰でも良い概念的理解を持っていると思いますが、おそらく少し急いでいて、そして多くの賢い人々のように、カリー化を部分的に適用することを混乱させました。これは少し残念ですが、理解できます(Paul Kingの回答を参照)。下位互換性を壊さずにこれを修正することは困難です。
私が提案した1つの解決策 は、curry
メソッドをオーバーロードして、引数が渡されないときにrealカリー化し、新しいpartial
関数を優先して、引数を指定したメソッドの呼び出しを非推奨にします。これは 少し奇妙に思えるかもしれません ですが、新しい引数を持つ(IMHO)醜い状況を回避しながら、引数なしで部分的なアプリケーションを使用する理由がないため、後方互換性を最大化します実際に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 } }
のように機能するはずなので、機能します。代わりに、ランタイム例外がスローされ、内部で少し死にます。
3つ以上の引数を持つ関数を考えると、グルーヴィーなカレーは実際には部分的なアプリケーションであることは明らかです。検討する
f :: (a,b,c) -> d
カレーの形は
fcurried :: a -> b -> c -> d
ただし、groovyのカレーは次のものと同等のものを返します(1つの引数xで呼び出された場合)
fgroovy :: (b,c) -> d
xに固定された値でfを呼び出します
つまり、groovyのカリーはN-1個の引数を持つ関数を返すことができますが、カリー化された関数は1つの引数しか持っていないため、groovyはカリーでカリー化できません
Groovyは、他の多くの非純粋なFP言語も部分的なアプリケーションに同様の命名法を使用しています-おそらくそのようなFP中心の機能に不幸なことです。 Groovyに含めるために提案されている実装です。実装について読むのに適したスレッドは次のとおりです。
http://groovy.markmail.org/thread/c4ycxdzm3ack6xxb
既存の機能は何らかの形で残り、新しいメソッドの名前などを呼び出すときに下位互換性が考慮されます。そのため、この段階では、新しい/古いメソッドの最終的な名前がどうなるかはわかりません。あります。おそらくネーミングの妥協案ですが、わかります。
ほとんどのOOプログラマーにとって、2つの用語(カリー化と部分的適用)の区別は間違いなく大部分が学術的です;ただし、慣れれば(そしてコードを保守する人は誰でもこれを読むように訓練されています)コーディングのスタイル)そして、ポイントフリーまたは暗黙のスタイルのプログラミング(これは「実際の」カリー化をサポートします)により、特定の種類のアルゴリズムをよりコンパクトに、場合によってはよりエレガントに表現できます。明らかに、見ている人の目には「美しさ」があります「ここでは、両方のスタイルをサポートする機能があることは、Groovyの性質(OO/FP、静的/動的、クラス/スクリプトなど)に準拠しています。
IBMで見つかったこの定義を考えると、
カレーという用語は、部分関数の概念を開発した数学者のHaskell Curryから取られました。カリー化とは、多数の引数を取る関数に複数の引数を取り、残りの引数を取り、結果を返す新しい関数を生成することを指します。
halver
は、新しい(カリー化された)関数(またはクロージャー)であり、パラメーターを1つだけ受け取ります。 halver(10)
を呼び出すと5になります。
したがって、n個の引数を持つ関数をn-1個の引数を持つ関数に変換します。同じことは、あなたのhaskellの例でもカレーが何をするかと言われています。