web-dev-qa-db-ja.com

カリー化と部分適用の違いは何ですか?

私はインターネット上で、他の人々のカレーの例はカレーではなく、実際には単なる部分的な適用であるというさまざまな苦情をよく目にします。

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

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

404
SpoonMeiser

カリー化は、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)のみを呼び出すと、部分的に適用された関数が返されます。戻り値はクロージャですxとyの値をlambda(z){z(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
234
Mark Cidade

それらがどのように異なるかを見る最も簡単な方法は、実際の例を考慮することです。入力として2つの数値を取り、出力として数値を返す関数Addがあると仮定しましょう。 Add(7, 5)12を返します。この場合:

  • 部分適用7の関数Addは、出力として新しい関数を提供します。その関数自体は、入力として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
    
  • Currying関数Addは、出力として新しい関数を提供します。その関数自体は、入力として1つの数値を取り、yet別の新しい関数を出力します。この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
    

言い換えると、「カレー」と「部分適用」はまったく異なる2つの機能です。 Curryingは正確に1つの入力を受け取りますが、部分的なアプリケーションは2(またはそれ以上)の入力を受け取ります。

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

151
Pacerier

注:これは F#Basics から引用したものです。関数型プログラミングを始める.NET開発者向けの優れた入門記事です。

カリー化とは、多くの引数を持つ関数を、それぞれが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関数が作成されると、multiply関数が部分的に適用されたと言う方が正確です。

乗算関数は、実際には一連の2つの関数です。最初の関数は、1つのint引数を取り、別の関数を返し、xを特定の値に効果的にバインドします。この関数は、yにバインドする値と考えることができるint引数も受け入れます。この2番目の関数を呼び出した後、xとyは両方ともバインドされるため、結果はdoubleの本体で定義されているxとyの積になります。

Doubleを作成するには、乗算関数のチェーンの最初の関数を評価して、乗算を部分的に適用します。結果の関数には、doubleという名前が付けられます。 doubleが評価されると、その引数と部分的に適用された値を使用して結果が作成されます。

48
dodgy_coder

興味深い質問。少し検索した後、 「部分関数アプリケーションがカリー化されていません」 が、私が見つけた最良の説明を与えました。 practicalの違いは私には特に明白だとは言えませんが、FPの専門家ではありません。 。

もう1つの便利なページ(まだ完全には読んでいませんが)は、 "Java Closuresを使用した部分的アプリケーション" です。

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

29
Jon Skeet

私は別のスレッドでこれに答えました https://stackoverflow.com/a/12846865/1685865 。つまり、部分関数アプリケーションは、特定の多変数関数の一部の引数を修正して引数の少ない別の関数を生成することです。一方、カリー化は、N個の引数の関数を単項関数を返す単項関数に変換することです... [カリー化はこの投稿の最後に示されています。]

カリー化はほとんど理論的に興味深いものです:単項関数のみを使用して計算を表現できます(つまり、every関数は単項です)。実際には、副産物として、言語に機能がカリー化されている場合、多くの有用な(ただしすべてではない)部分的な機能アプリケーションを簡単にすることができる手法です。繰り返しますが、部分的なアプリケーションを実装する唯一の手段ではありません。そのため、部分的な適用が他の方法で行われるシナリオに遭遇する可能性がありますが、人々はそれをカリー化と間違えています。

(カレーの例)

実際には、単に書くだけではありません

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

カリー化のために。

13
Ji Han

カリー化は、関数fを取り、新しい関数hを返すone引数の関数です。 hXから引数を取り、YZにマップするfunctionを返すことに注意してください。

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

部分適用は、関数ffへの1つ以上の追加引数を取り、新しい関数gを返すtwo(or more) argumentsの関数です。

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

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

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

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

この場合、カリー化は1つの引数の関数を返しますが、部分的なアプリケーションは複数の引数の関数を返します。

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

ソース: Wikipedia Currying

6
Roland

カレーと部分的なアプリケーションの違いは、次の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)のようなものが表示されている場合、実際には部分的な適用ではありません。

参考文献:

5
gsklee

簡単な答え

Curry:を使用すると、関数を呼び出して、複数の呼び出しに分割し、呼び出しごとに1つの引数を指定できます。

Partial:を使用すると、関数を呼び出して、複数の呼び出しに分割し、呼び出しごとに複数の引数を指定できます。


簡単なヒント

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

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


シンプルなe(c)(サンプル)

(Javascriptで)

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

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

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

subject1およびfoobarで呼び出す

processSubject('subject1');
processSubject('foobar');

快適ですね。 ????

curryingを使用すると、1回につき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
Kamafeather

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

2
sunny-mittal

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

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

2
sunny-mittal

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

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

2

ここには他にも素晴らしい答えがありますが、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
Tobogganski

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

不愉快は非常に明確に定義されていません(または、むしろ、すべてがアイデアの精神を捕らえる「矛盾する」定義があります)。基本的に、複数の引数を取る関数を、単一の引数を取る関数に変えることを意味します。例えば、

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

0
nomen

この質問をするほとんどの人はすでに基本的な概念に精通しているので、それについて話す必要はないと思います。紛らわしい部分はオーバーラップです。

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

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

Curryingdefine関数の場合です。

Partial Applicationは、call関数の場合です。

Applicationは、関数を呼び出すための数学用語です。

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

0
Brennan Cheung