473

他の人のカリー化の例はカリー化ではなく、実際には部分適用にすぎないというさまざまな苦情をインターネットでよく目にします。

部分適用とは何か、またはカリー化とどのように違うのかについての適切な説明は見つかりませんでした。一般的な混乱があるようです。同等の例は、ある場所ではカリー化、他の場所では部分適用として説明されています。

誰かが私に両方の用語の定義とそれらがどのように異なるかについての詳細を教えてもらえますか?

4

15 に答える 15

277

カリー化とは、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 yor 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
于 2008-10-20T11:02:57.453 に答える
176

それらがどのように異なるかを確認する最も簡単な方法は、実際の例を検討することです。Add入力として 2 つの数値を取り、出力として数値を返すAdd(7, 5)関数があると仮定しましょう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 つ (またはそれ以上) の入力を必要とします。

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

于 2014-05-02T23:26:31.313 に答える
56

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

カリー化とは、多くの引数を持つ関数を、それぞれが 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

多くの場合、人々は、multiply をカリー化して double を形成すると誤って言います。しかし、これはある程度正しいだけです。乗算関数はカリー化されていますが、F# の関数は既定でカリー化されているため、それが定義されている場合に発生します。double 関数が作成されると、multiply 関数が部分的に適用されたと言った方が正確です。

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

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

于 2012-05-04T05:18:33.213 に答える
37

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

もう 1 つの便利なページ (まだ完全には読んでいないことを告白します) は、"Currying and Partial Application with Java Closures"です。

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

于 2008-10-20T11:02:06.737 に答える
17

別のスレッド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

カリー化のために。

于 2012-10-11T19:40:41.990 に答える
10

カレーと部分適用の違いは、次の JavaScript の例で最もよく説明できます。

function f(x, y, z) {
    return x + y + z;
}

var partial = f.bind(null, 1);

6 === partial(2, 3);

部分的に適用すると、関数のアリティが小さくなります。上記の例でfは、アリティは 3partialですが、アリティは 2 しかありません。さらに重要なことに、部分的に適用された関数は、カリー化チェーンの別の関数ではなく、invoke の直後に結果を返します。したがって、partial(2)(3)のようなものが表示されている場合、実際には部分適用ではありません。

参考文献:

于 2013-05-27T03:51:08.067 に答える
5

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

部分適用とカリー化の両方で、一度にすべてではなく、関数に引数を提供する必要があります。かなり標準的な例は、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 実装では、arity (最終評価での引数の総数) を部分的なアプリケーション実装に渡すことができます。これにより、2 つの説明が混同されて混乱する可能性があります...しかし、その時点で、2 つの手法は大きく交換可能。

于 2017-11-13T02:30:17.730 に答える
4

私にとって、部分適用は、使用された引数が結果の関数に完全に統合される新しい関数を作成する必要があります。

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

于 2013-05-13T10:03:40.747 に答える
4

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

于 2015-12-07T05:00:23.313 に答える
3

ここには他にも素晴らしい答えがありますが、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 つの関数が得られます。この場合、partial-application は、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 );
}
于 2017-02-27T21:19:31.123 に答える
1

これを書いているとき、私はカリー化とアンカリー化を混同しました。それらは関数の逆変換です。変換とその逆が何を表しているかを理解する限り、どちらを何と呼ぶか​​は問題ではありません。

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

(+) :: Int -> Int -> Int

では、これを 1 つの引数を取る関数にするにはどうすればよいでしょうか。もちろん、ごまかします!

plus :: (Int, Int) -> Int

plus が 1 つの引数を取るようになったことに注意してください (これは 2 つのもので構成されています)。素晴らしい!

これのポイントは何ですか?2 つの引数を取る関数があり、引数のペアがある場合、その関数を引数に適用しても期待どおりの結果が得られることを知っておくと便利です。実際、それを行うための配管は既に存在するため、明示的なパターン マッチングなどを行う必要はありません。あなたがしなければならないことは次のとおりです。

(uncurry (+)) (1,2)

では、部分関数適用とは何でしょう? 2 つの引数を持つ関数を 1 つの引数を持つ関数に変換するのは、別の方法です。ただし、動作は異なります。ここでも (+) を例に取りましょう。単一の Int を引数として取る関数にするにはどうすればよいでしょうか? カンニング!

((+) 0) :: Int -> Int

これは、任意の Int にゼロを追加する関数です。

((+) 1) :: Int -> Int

任意の Int に 1 を加算します。これらの場合のそれぞれで、(+) は「部分適用」です。

于 2014-04-09T03:29:17.213 に答える