カレーと部分塗布の違いは何ですか?


438

他の人々のカレーの例はカレーではないが、実際には部分的な適用にすぎないというさまざまな不満をインターネットでよく目にします。

私は部分的なアプリケーションが何であるか、またはそれがカレーとどのように異なるかについてのきちんとした説明を見つけませんでした。一般的な混乱があり、同等の例がカレー化されている場所と、部分的に適用されているところがあると説明されています。

誰かが私に両方の用語の定義とそれらの違いの詳細を教えてもらえますか?

回答:


256

カリー化とは、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 zf 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

40
部分的な適用とは、関数をカリー化し、結果の関数のすべてではなく一部を使用することだと言っていますか?
SpoonMeiser 2008年

9
多かれ少なかれ、はい。引数のサブセットのみを指定すると、残りの引数を受け入れる関数が返されます
Mark Cidade

1
関数f(a、b、c、d)をg(a、b)に変更すると、部分的なアプリケーションとしてカウントされますか?それとも、カレー関数に適用された場合のみですか?苦労して申し訳ありませんが、私はここで明確な答えを求めています。
SpoonMeiser 2008年

2
@マーク:これは、私を思い起こさせるコンセプトの1つに過ぎないと思いますが、信頼できる情報源への訴えは、それらすべてが互いに指摘しているように見えるため、満足することはほとんどありません。ウィキペディアは私が権威ある情報源と考えるものではありませんが、他に多くを見つけるのは難しいと私は理解しています。自国語の詳細に同意できる(または同意できない)かどうかに関係なく、私たちが話していることとその力はどちらも知っていると私は思う。:)マークありがとう!
Jason Bunting 2010

5
@JasonBunting、あなたの最初のコメントに関して、あなたが話していたのは不明瞭です。カリー化とは、複数の引数を持つ関数を入力として受け取り、1つの引数を持つ関数のチェーンを出力として返すことです。デカリー化とは、1引数の関数のチェーンを入力として受け取り、複数引数の関数を出力として返すことです。stackoverflow.com/a/23438430/632951で
Pacerier

165

それらの違いを確認する最も簡単な方法は、実際の例を検討することですAdd2つの数値を入力として受け取り、数値を出力として返す関数があるとします(例: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つ(またはそれ以上)の入力を受け取ります。

どちらも出力として関数を返しますが、返される関数は上記で示したようにまったく異なる形式です。


24
部分的なアプリケーションは、関数をn-arytoに変換し、から(x - n)-aryカリー化n-aryn * 1-aryます。部分的に適用された関数は、(アプリケーションの)スコープAdd7が狭くなりAddます。つまり、よりも表現力が低下します。一方、カレー関数は元の関数と同じくらい表現力があります。
ボブ

4
より特徴的な特性は、f(x、y、z)=> Rをカレー化すると、g(y)=> h(z)=> Rを返すf(x)が得られ、それぞれが単一の引数を消費することです。しかし、f(x、y、z)をf(x)として部分的に適用すると、g(y、z)=> R、つまり2つの引数が得られます。その特性がなければ、カリー化は0引数への部分的な適用のようであり、したがってすべての引数をバインドしないままにすることができます。ただし、実際には、0引数に部分的に適用されるf()は、カリー化されたf()とは異なり、一度に3つの引数を消費する関数です。
マクシムグメロフ2017

2
この場合も、正解は最初の投票でも最も投票数の多い投票でもありません。この回答の最後にあるカレーと部分的な署名の簡単な説明が、質問を解決する最も簡単な方法です。
fnl 2017

2
コメントはf2(7)(5) is just a syntactic shortcutどういう意味ですか?(私はほとんど知りません。)f27をまだ含んでいない/「知っている」のですか?
Zach Mierzejewski

@Pacerier、curry実装はどこかにあります(それがにあるとは思わないfunctools
alancalvitti

51

注:これは、関数プログラミングに取り組む.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が評価されると、部分的に適用された値とともに引数を使用して結果を作成します。


33

興味深い質問です。少し検索したところ、「部分的な機能のアプリケーションはカレー化されていません」が、私が見つけた最良の説明を提供しました。実用的とは言えません違いが特に明白であるとはませんが、私はFPの専門家ではありません...

別の有用に見えるページ(まだ完全に読んでいないと私は告白します)は、「Javaクロージャーによるカリー化および部分的アプリケーション」です。です。

これは、広く混同されている用語のペアのように見えます。


5
最初のリンクは違いについてのスポットです。ここに私が便利だと思った別のものがあります:bit.ly/CurryingVersusPartialApplication
Jason Bunting

5
カリー化とは、タプルを使用することです(タプルの引数を取る関数を、n個の個別の引数を取る関数に、またはその逆に変換します)。部分適用とは、関数を一部の引数に適用して、残りの引数に新しい関数を生成する機能です。カリー化==がタプルと関係があると考えれば、覚えやすいでしょう。
Don Stewart

9
あなたが投稿した@Jonリンクは有益ですが、あなたの答えを拡張し、ここにいくつかの情報を追加することをお勧めします。
Zaheer Ahmed 2014

2
@Jonmeta.stackoverflow.com/questions/275464/…を参照してください
user247702

11
いくつかのリンクに20の賛成票があり、カレーと部分的なアプリケーションの違いを本当に知らない入場があるとは信じられません。よく演奏されました。
AlienWebguy 2015

16

私は別のスレッドでこれに答えました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
では、カレーは部分的な適用の特定のケースだと思いますか?
SpoonMeiser

1
@SpoonMeiser、いいえ、カリー化は部分適用の特定のケースではありません。2入力関数の部分適用は、関数のカリー化と同じではありません。stackoverflow.com/a/23438430/632951を参照してください。
Pacerier、2014年

10

カリー化は、関数を取り、新しい関数を返す1つの引数のf関数hです。注hからの引数を取るXと返す関数にマッピングYするにZ

curry(f) = h 
f: (X x Y) -> Z 
h: X -> (Y -> Z)

部分的なアプリケーションは、関数と1つ以上の追加の引数を取り、新しい関数を返す2つ以上の引数の関数です。ffg

part(f, 2) = g
f: (X x Y) -> Z 
g: Y -> Z

2つの引数を持つ関数では、次の等式が成り立つため、混乱が生じます。

partial(f, a) = curry(f)(a)

両側は同じ1つの引数の関数を生成します。

この場合、カリー化は1つの引数の関数を返し、部分的なアプリケーションは複数の引数の関数を返すため、同等性はより高いarity関数には当てはまりません。

違いは動作にもありますが、カリー化は元の関数全体を再帰的に(引数ごとに1回)変換しますが、部分的な適用は1ステップの置き換えにすぎません。

出典:Wikipedia Currying


8

カレーアプリケーションと部分アプリケーションの違いは、次の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ステップ掘り下げるのとは異なり、結果を返します。しかし、実際には残りのすべての引数を指定する必要があると誰も言いません。部分的な適用の結果を部分的に適用することができ、それは再び「結果」ではなく関数になります
Maksim Gumerov

6

簡単な答え

カレー:関数を呼び出して、複数の呼び出しに分割し、呼び出しごとに1つの引数を提供します。

部分的:関数を呼び出して複数の呼び出しに分割し、呼び出しごとに複数の引数を提供します。


シンプルなヒント

どちらも、より少ない引数を提供する(または、より適切には、それらを累積的に提供する)関数を呼び出すことができます。実際には、どちらも(呼び出しごとに)特定の値を関数の特定の引数にバインドします。

実際の違いは、関数に2つ以上の引数がある場合に見られます。


単純なe(c)(サンプル)

(JavaScript)

function process(context, success_callback, error_callback, subject) {...}

コンテキストとコールバックのように、常に同じである場合に、常に引数を渡すのはなぜですか?関数のいくつかの値をバインドするだけです

processSubject = _.partial(process, my_context, my_success, my_error)

そして、subject1foob​​arでそれを呼び出します

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');

免責事項

私はすべての学問的/数学的な説明をスキップしました。原因はわかりません。多分それは助けました🙃


4

私は学習中にこの質問をたくさんし、それ以来何度も尋ねられました。私が違いを説明できる最も簡単な方法は、両方が同じであるということです:)説明させてください...明らかに違いがあります。

部分的な適用とカリー化の両方で、おそらく一度にすべてではなく、関数に引数を提供する必要があります。かなり標準的な例は、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の実装では、アリティ(最終評価の引数の総数)を部分的なアプリケーションの実装に渡すことができます。主に互換性があります。


3

私にとって、部分的なアプリケーションは、使用された引数が結果の関数に完全に統合される新しい関数を作成する必要があります。

ほとんどの関数型言語は、クロージャーを返すことでカリー化を実装します。部分的に適用する場合はラムダで評価しないでください。したがって、部分適用を面白くするには、カリーと部分適用を区別し、部分適用をカリーとラムダでの評価と見なす必要があります。


3

理論的な数学や関数型プログラミングには強いバックグラウンドがないので、私はここで非常に間違っている可能性がありますが、簡単な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を試す]をクリックしてコードを貼り付け、コンパイルされたバージョンを確認してください。ありがとう!


2

この質問をするほとんどの人は、基本的な概念にすでに精通しているので、そのことについて話す必要はないと想定します。混乱する部分は重複です。

概念を完全に使用できるかもしれませんが、この疑似原子アモルファス概念のぼかしとしてそれらを一緒に理解します。不足しているのは、それらの間の境界がどこにあるかを知ることです。

それぞれが何であるかを定義する代わりに、それらの違い、つまり境界だけを強調する方が簡単です。

カリー化とは、関数を定義することです。

部分適用は、関数を呼び出すときです。

アプリケーションは、関数を呼び出すための数学の話です。

一部のアプリケーションでは、カリー化された関数を呼び出し、戻り値の型として関数を取得する必要があります。


1

ここには他にもすばらしい答えがありますが、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 );
}

次の重要な洞察が得られました。「カリー化により、関数を作成するための引数が1つの関数が提供され、部分的なアプリケーションが1つ以上の引数をハードコーディングするラッパー関数を作成します。」
Roland

0

これを書いているとき、私はカレーとカレーを混同しました。これらは関数の逆変換です。変換とその逆が表すものを取得する限り、それを何と呼んでもかまいません。

アンカリーは明確に定義されていません(または、アイデアの精神を捉えた「矛盾する」定義があります)。基本的には、複数の引数を取る関数を単一の引数を取る関数に変換することを意味します。例えば、

(+) :: 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を追加します。等これらのケースでは、(+)が「部分的に適用」されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.