他の人々のカレーの例はカレーではないが、実際には部分的な適用にすぎないというさまざまな不満をインターネットでよく目にします。
私は部分的なアプリケーションが何であるか、またはそれがカレーとどのように異なるかについてのきちんとした説明を見つけませんでした。一般的な混乱があり、同等の例がカレー化されている場所と、部分的に適用されているところがあると説明されています。
誰かが私に両方の用語の定義とそれらの違いの詳細を教えてもらえますか?
他の人々のカレーの例はカレーではないが、実際には部分的な適用にすぎないというさまざまな不満をインターネットでよく目にします。
私は部分的なアプリケーションが何であるか、またはそれがカレーとどのように異なるかについてのきちんとした説明を見つけませんでした。一般的な混乱があり、同等の例がカレー化されている場所と、部分的に適用されているところがあると説明されています。
誰かが私に両方の用語の定義とそれらの違いの詳細を教えてもらえますか?
回答:
カリー化とは、n個の引数を持つ単一の関数を、それぞれが単一の引数を持つn個の関数に変換することです。次の関数があるとします。
function f(x,y,z) { z(x(y));}
カレー化すると、次のようになります。
function f(x) { lambda(y) { lambda(z) { z(x(y)); } } }
f(x、y、z)の完全なアプリケーションを取得するには、次のようにする必要があります。
f(x)(y)(z);
多くの関数型言語で記述できますf x y z
。f x y
またはf(x)(y)のみを呼び出すと、部分的に適用された関数が返されます。戻り値は、lambda(z){z(x(y))}
xとyの値が渡されたのクロージャですf(x,y)
。
部分的なアプリケーションを使用する1つの方法は、関数をfoldなどの一般化された関数の部分的なアプリケーションとして定義することです。
function fold(combineFunction, accumulator, list) {/* ... */}
function sum = curry(fold)(lambda(accum,e){e+accum}))(0);
function length = curry(fold)(lambda(accum,_){1+accum})(empty-list);
function reverse = curry(fold)(lambda(accum,e){concat(e,accum)})(empty-list);
/* ... */
@list = [1, 2, 3, 4]
sum(list) //returns 10
@f = fold(lambda(accum,e){e+accum}) //f = lambda(accumulator,list) {/*...*/}
f(0,list) //returns 10
@g = f(0) //same as sum
g(list) //returns 10
それらの違いを確認する最も簡単な方法は、実際の例を検討することです。Add
2つの数値を入力として受け取り、数値を出力として返す関数があるとします(例:Add(7, 5)
return)12
。この場合:
関数Add
に値7
を部分的に適用すると、出力として新しい関数が得られます。その関数自体が1つの数値を入力として取り、数値を出力します。など:
Partial(Add, 7); // returns a function f2 as output
// f2 takes 1 number as input and returns a number as output
これを行うことができます:
f2 = Partial(Add, 7);
f2(5); // returns 12;
// f2(7)(5) is just a syntactic shortcut
関数Add
をカリー化すると、出力として新しい関数が得られます。その関数自体は1つの数値を入力として取り、さらに別の新しい関数を出力します。この3番目の関数は、1つの数値を入力として受け取り、数値を出力として返します。など:
Curry(Add); // returns a function f2 as output
// f2 takes 1 number as input and returns a function f3 as output
// i.e. f2(number) = f3
// f3 takes 1 number as input and returns a number as output
// i.e. f3(number) = number
これを行うことができます:
f2 = Curry(Add);
f3 = f2(7);
f3(5); // returns 12
つまり、「カレー化」と「部分塗布」は全く別の機能です。カリー化は正確に1つの入力を受け取りますが、部分適用は2つ(またはそれ以上)の入力を受け取ります。
どちらも出力として関数を返しますが、返される関数は上記で示したようにまったく異なる形式です。
n-ary
toに変換し、から(x - n)-ary
カリー化n-ary
しn * 1-ary
ます。部分的に適用された関数は、(アプリケーションの)スコープAdd7
が狭くなりAdd
ます。つまり、よりも表現力が低下します。一方、カレー関数は元の関数と同じくらい表現力があります。
f2(7)(5) is just a syntactic shortcut
どういう意味ですか?(私はほとんど知りません。)f2
7をまだ含んでいない/「知っている」のですか?
curry
実装はどこかにあります(それがにあるとは思わないfunctools
)
注:これは、関数プログラミングに取り組む.NET開発者向けの優れた入門記事であるF#Basicsから取られました。
カリー化とは、多くの引数を持つ関数を、それぞれが1つの引数を取り、最終的に元の関数と同じ結果を生成する一連の関数に分割することを意味します。特に部分的なアプリケーションと混同されることが多いため、関数型プログラミングを初めて使用する開発者にとって、カリー化はおそらく最も困難なトピックです。この例では、両方が動作していることがわかります。
let multiply x y = x * y let double = multiply 2 let ten = double 5
すぐに、ほとんどの命令型言語とは異なる動作が表示されるはずです。2番目のステートメントは、2つの引数を取る関数に1つの引数を渡すことにより、doubleと呼ばれる新しい関数を作成します。結果は、1つのint引数を受け入れ、xが2でyがその引数と等しい乗算を呼び出した場合と同じ出力を生成する関数です。動作に関しては、次のコードと同じです。
let double2 z = multiply 2 z
しばしば、掛け算はカリー化されてダブルを形成すると誤って言われます。しかし、これはいくぶん本当です。乗算関数はカリー化されていますが、F#の関数はデフォルトでカリー化されているため、それが定義されている場合に発生します。double関数が作成されると、乗算関数が部分的に適用されていると言う方が正確です。
乗算関数は、実際には一連の2つの関数です。最初の関数は1つのint引数を取り、別の関数を返し、xを特定の値に効果的にバインドします。この関数は、yにバインドする値と考えることができるint引数も受け入れます。この2番目の関数を呼び出した後、xとyの両方がバインドされるため、結果はdoubleの本体で定義されているxとyの積になります。
doubleを作成するには、乗算関数のチェーンの最初の関数を評価して、部分的に乗算を適用します。結果の関数にはdoubleという名前が付けられます。doubleが評価されると、部分的に適用された値とともに引数を使用して結果を作成します。
興味深い質問です。少し検索したところ、「部分的な機能のアプリケーションはカレー化されていません」が、私が見つけた最良の説明を提供しました。実用的とは言えません違いが特に明白であるとはませんが、私はFPの専門家ではありません...
別の有用に見えるページ(まだ完全に読んでいないと私は告白します)は、「Javaクロージャーによるカリー化および部分的アプリケーション」です。です。
これは、広く混同されている用語のペアのように見えます。
私は別のスレッドでこれに答えましたhttps://stackoverflow.com/a/12846865/1685865。要するに、部分的な関数の適用は、与えられた多変数関数のいくつかの引数を修正して引数の少ない別の関数を生成することであり、カリー化はN個の引数の関数を単項関数を返す単項関数に変換することです... [例カレーはこの投稿の最後に表示されます。]
カリー化は主に理論的に興味深いものです。単項関数のみを使用して計算を表現できます(つまり、すべての関数は単項です)。実際には、副産物として、言語にカリー化された関数がある場合、それは多くの有用な(すべてではない)部分関数型アプリケーションを簡単にすることができる技法です。この場合も、部分的なアプリケーションを実装する唯一の手段ではありません。そのため、部分的な適用が別の方法で行われるシナリオに遭遇する可能性がありますが、人々はそれをカリー化と誤解しています。
(カレーの例)
実際には、ただ書くだけではありません
lambda x: lambda y: lambda z: x + y + z
または同等のjavascript
function (x) { return function (y){ return function (z){ return x + y + z }}}
の代わりに
lambda x, y, z: x + y + z
カレーのために。
カリー化は、関数を取り、新しい関数を返す1つの引数のf
関数h
です。注h
からの引数を取るX
と返す関数にマッピングY
するにZ
:
curry(f) = h
f: (X x Y) -> Z
h: X -> (Y -> Z)
部分的なアプリケーションは、関数と1つ以上の追加の引数を取り、新しい関数を返す2つ以上の引数の関数です。f
f
g
part(f, 2) = g
f: (X x Y) -> Z
g: Y -> Z
2つの引数を持つ関数では、次の等式が成り立つため、混乱が生じます。
partial(f, a) = curry(f)(a)
両側は同じ1つの引数の関数を生成します。
この場合、カリー化は1つの引数の関数を返し、部分的なアプリケーションは複数の引数の関数を返すため、同等性はより高いarity関数には当てはまりません。
違いは動作にもありますが、カリー化は元の関数全体を再帰的に(引数ごとに1回)変換しますが、部分的な適用は1ステップの置き換えにすぎません。
カレーアプリケーションと部分アプリケーションの違いは、次のJavaScriptの例で最もよくわかります。
function f(x, y, z) {
return x + y + z;
}
var partial = f.bind(null, 1);
6 === partial(2, 3);
部分的に適用すると、より小さなアリティの関数になります。上記の例でf
は、アリティは3 partial
ですが、アリティは2しかありません。さらに重要なことに、部分的に適用された関数は、カリーチェーンの別の関数ではなく、invokeの直後に結果を返します。したがって、のようなものが表示されている場合partial(2)(3)
、それは実際には部分的なアプリケーションではありません。
参考文献:
カレー:関数を呼び出して、複数の呼び出しに分割し、呼び出しごとに1つの引数を提供します。
部分的:関数を呼び出して複数の呼び出しに分割し、呼び出しごとに複数の引数を提供します。
どちらも、より少ない引数を提供する(または、より適切には、それらを累積的に提供する)関数を呼び出すことができます。実際には、どちらも(呼び出しごとに)特定の値を関数の特定の引数にバインドします。
実際の違いは、関数に2つ以上の引数がある場合に見られます。
(JavaScript)
function process(context, success_callback, error_callback, subject) {...}
コンテキストとコールバックのように、常に同じである場合に、常に引数を渡すのはなぜですか?関数のいくつかの値をバインドするだけです
processSubject = _.partial(process, my_context, my_success, my_error)
そして、subject1とfoobarでそれを呼び出します
processSubject('subject1');
processSubject('foobar');
快適ですね。😉
ではカリーあなたは時間ごとに1つの引数を渡す必要があるだろう
curriedProcess = _.curry(process);
processWithBoundedContext = curriedProcess(my_context);
processWithCallbacks = processWithBoundedContext(my_success)(my_error); // note: these are two sequential calls
result1 = processWithCallbacks('subject1');
// same as: process(my_context, my_success, my_error, 'subject1');
result2 = processWithCallbacks('foobar');
// same as: process(my_context, my_success, my_error, 'foobar');
私はすべての学問的/数学的な説明をスキップしました。原因はわかりません。多分それは助けました🙃
私は学習中にこの質問をたくさんし、それ以来何度も尋ねられました。私が違いを説明できる最も簡単な方法は、両方が同じであるということです:)説明させてください...明らかに違いがあります。
部分的な適用とカリー化の両方で、おそらく一度にすべてではなく、関数に引数を提供する必要があります。かなり標準的な例は、2つの数値を加算することです。疑似コード(実際にはキーワードなしのJS)では、基本関数は次のようになります。
add = (x, y) => x + y
「addOne」関数が必要な場合は、部分的に適用するか、カレーすることができます。
addOneC = curry(add, 1)
addOneP = partial(add, 1)
今ではそれらを使用することは明らかです:
addOneC(2) #=> 3
addOneP(2) #=> 3
違いは何ですか?まあ、それは微妙ですが、部分的なアプリケーションはいくつかの引数を提供することを含み、返された関数は次の呼び出し時にメイン関数を実行しますが、カリーは必要なすべての引数を持つまで待機し続けます:
curriedAdd = curry(add) # notice, no args are provided
addOne = curriedAdd(1) # returns a function that can be used to provide the last argument
addOne(2) #=> returns 3, as we want
partialAdd = partial(add) # no args provided, but this still returns a function
addOne = partialAdd(1) # oops! can only use a partially applied function once, so now we're trying to add one to an undefined value (no second argument), and we get an error
簡単に言うと、部分的なアプリケーションを使用して一部の値を事前入力します。次にメソッドを呼び出すと、メソッドが実行され、未定義の引数はすべて未定義のままになります。関数のシグネチャを満たすために必要な回数だけ継続的に部分的に適用された関数を返したい場合は、カリー化を使用します。最後に考案された例:
curriedAdd = curry(add)
curriedAdd()()()()()(1)(2) # ugly and dumb, but it works
partialAdd = partial(add)
partialAdd()()()()()(1)(2) # second invocation of those 7 calls fires it off with undefined parameters
お役に立てれば!
更新:一部の言語またはlibの実装では、アリティ(最終評価の引数の総数)を部分的なアプリケーションの実装に渡すことができます。主に互換性があります。
私にとって、部分的なアプリケーションは、使用された引数が結果の関数に完全に統合される新しい関数を作成する必要があります。
ほとんどの関数型言語は、クロージャーを返すことでカリー化を実装します。部分的に適用する場合はラムダで評価しないでください。したがって、部分適用を面白くするには、カリーと部分適用を区別し、部分適用をカリーとラムダでの評価と見なす必要があります。
理論的な数学や関数型プログラミングには強いバックグラウンドがないので、私はここで非常に間違っている可能性がありますが、簡単なFPへの進出から、カリー化はN個の引数の関数を1つの引数のN個の関数に変換する傾向があるようです。一方、部分的な適用は、[実際には]引数の数が不定である可変個関数でより効果的に機能します。以前の回答の例のいくつかはこの説明に反することを知っていますが、それは私が概念を分離するのに最も役立ちました。次の例を考えてみてください(簡潔にするためにCoffeeScriptで記述しています。混乱を招く場合はお詫びしますが、必要に応じて説明を求めてください)。
# partial application
partial_apply = (func) ->
args = [].slice.call arguments, 1
-> func.apply null, args.concat [].slice.call arguments
sum_variadic = -> [].reduce.call arguments, (acc, num) -> acc + num
add_to_7_and_5 = partial_apply sum_variadic, 7, 5
add_to_7_and_5 10 # returns 22
add_to_7_and_5 10, 11, 12 # returns 45
# currying
curry = (func) ->
num_args = func.length
helper = (prev) ->
->
args = prev.concat [].slice.call arguments
return if args.length < num_args then helper args else func.apply null, args
helper []
sum_of_three = (x, y, z) -> x + y + z
curried_sum_of_three = curry sum_of_three
curried_sum_of_three 4 # returns a function expecting more arguments
curried_sum_of_three(4)(5) # still returns a function expecting more arguments
curried_sum_of_three(4)(5)(6) # returns 15
curried_sum_of_three 4, 5, 6 # returns 15
これは明らかに不自然な例ですが、任意の数の引数を受け入れる関数を部分的に適用すると、いくつかの予備データを使用して関数を実行できるようになります。関数のカリー化も同様ですが、N個のパラメーターすべてが説明されるまで、N個のパラメーター関数を分割して実行することができます。
繰り返しますが、これは私が読んだことからの私の見解です。誰かが同意しない場合は、すぐに反対票を投じるのではなく、理由についてコメントをいただければ幸いです。また、CoffeeScriptが読みにくい場合は、coffeescript.orgにアクセスし、[coffeescriptを試す]をクリックしてコードを貼り付け、コンパイルされたバージョンを確認してください。ありがとう!
この質問をするほとんどの人は、基本的な概念にすでに精通しているので、そのことについて話す必要はないと想定します。混乱する部分は重複です。
概念を完全に使用できるかもしれませんが、この疑似原子アモルファス概念のぼかしとしてそれらを一緒に理解します。不足しているのは、それらの間の境界がどこにあるかを知ることです。
それぞれが何であるかを定義する代わりに、それらの違い、つまり境界だけを強調する方が簡単です。
カリー化とは、関数を定義することです。
部分適用は、関数を呼び出すときです。
アプリケーションは、関数を呼び出すための数学の話です。
一部のアプリケーションでは、カリー化された関数を呼び出し、戻り値の型として関数を取得する必要があります。
ここには他にもすばらしい答えがありますが、Javaでのこの例(私の理解による)は一部の人々にとって有益かもしれないと思います。
public static <A,B,X> Function< B, X > partiallyApply( BiFunction< A, B, X > aBiFunction, A aValue ){
return b -> aBiFunction.apply( aValue, b );
}
public static <A,X> Supplier< X > partiallyApply( Function< A, X > aFunction, A aValue ){
return () -> aFunction.apply( aValue );
}
public static <A,B,X> Function< A, Function< B, X > > curry( BiFunction< A, B, X > bif ){
return a -> partiallyApply( bif, a );
}
したがって、カリー化により、関数を作成するための1つの引数の関数が提供されます。部分的なアプリケーションでは、1つ以上の引数をハードコーディングするラッパー関数を作成します。
コピーして貼り付ける場合、次の方法はノイズが多くなりますが、型の方が寛大なので扱いやすいです。
public static <A,B,X> Function< ? super B, ? extends X > partiallyApply( final BiFunction< ? super A, ? super B, X > aBiFunction, final A aValue ){
return b -> aBiFunction.apply( aValue, b );
}
public static <A,X> Supplier< ? extends X > partiallyApply( final Function< ? super A, X > aFunction, final A aValue ){
return () -> aFunction.apply( aValue );
}
public static <A,B,X> Function< ? super A, Function< ? super B, ? extends X > > curry( final BiFunction< ? super A, ? super B, ? extends X > bif ){
return a -> partiallyApply( bif, a );
}
これを書いているとき、私はカレーとカレーを混同しました。これらは関数の逆変換です。変換とその逆が表すものを取得する限り、それを何と呼んでもかまいません。
アンカリーは明確に定義されていません(または、アイデアの精神を捉えた「矛盾する」定義があります)。基本的には、複数の引数を取る関数を単一の引数を取る関数に変換することを意味します。例えば、
(+) :: Int -> Int -> Int
では、これを単一の引数を取る関数にどのように変換しますか?もちろんごまかす!
plus :: (Int, Int) -> Int
plusは単一の引数(2つの要素で構成される)を取ることに注意してください。素晴らしい!
これの意味は何ですか?2つの引数を取る関数があり、引数のペアがある場合、関数を引数に適用しても、期待どおりの結果が得られることを知っておくと便利です。そして実際、それを行うための配管はすでに存在しているので、明示的なパターンマッチングのようなことをする必要はありません。あなたがしなければならないすべてはです:
(uncurry (+)) (1,2)
では、部分関数適用とは何でしょうか?これは、2つの引数の関数を1つの引数を持つ関数に変換する別の方法です。ただし、動作は異なります。もう一度、例として(+)を取り上げます。単一のIntを引数として取る関数にどのように変換できるでしょうか。カンニング!
((+) 0) :: Int -> Int
これは、任意のIntにゼロを追加する関数です。
((+) 1) :: Int -> Int
Intに1を追加します。等これらのケースでは、(+)が「部分的に適用」されます。