741

いくつかの記事やブログでカリー化された関数への言及を見てきましたが、適切な説明 (または少なくとも 1 つでも意味のあるもの) を見つけることができません。

4

22 に答える 22

989

カリー化とは、複数の引数をとる関数を、それぞれが 1 つの引数しかとらない一連の関数に分解することです。JavaScript での例を次に示します。

function add (a, b) {
  return a + b;
}

add(3, 4); // returns 7

これは、2 つの引数 a と b を取り、それらの合計を返す関数です。この関数をカリー化します。

function add (a) {
  return function (b) {
    return a + b;
  }
}

これは、1 つの引数aを受け取り、別の引数 を受け取る関数をb返す関数であり、その関数はそれらの合計を返します。

add(3)(4);

var add3 = add(3);

add3(4);

最初のステートメントは、ステートメントと同様に 7 を返しますadd(3, 4)。2 番目のステートメントは、add3引数に 3 を追加する、呼び出される新しい関数を定義します。(これをクロージャーと呼ぶ人もいます。) 3 番目のステートメントは、add3演算を使用して 3 を 4 に加算し、結果として再び 7 を生成します。

于 2008-08-30T20:19:51.187 に答える
134

関数の代数では、複数の引数(またはNタプルである同等の1つの引数)をとる関数を扱うことはややエレガントではありませんが、MosesSchönfinkel(および独立してHaskell Curry)が証明したように、それは必要ありません。必要なのは、1つの引数を取る関数です。

では、たとえば、自然に表現するものにどのように対処しますf(x,y)か?f(x)(y)さて、あなたはそれを-と同等と見なし、f(x)それを関数と呼びg、その関数をに適用しますy。言い換えると、1つの引数を取る関数しかありませんが、それらの関数の一部は他の関数を返します(これも1つの引数を取ります;-)。

いつものように、ウィキペディアにはこれに関する素晴らしい要約エントリがあり、多くの有用なポインタ(おそらくあなたの好きな言語に関するものを含む;-)と少し厳密な数学的処理があります。

于 2009-08-30T02:08:19.733 に答える
112

具体的な例を次に示します。

オブジェクトに作用する重力を計算する関数があるとします。式がわからない場合は、ここで見つけることができます。この関数は、必要な 3 つのパラメーターを引数として受け取ります。

さて、地球上にいるので、この惑星上の物体の力だけを計算したいとします。関数型言語では、地球の質量を関数に渡して、部分的に評価することができます。返されるのは、2 つの引数のみを取り、地球上のオブジェクトの重力を計算する別の関数です。これをカリー化といいます。

于 2009-08-30T02:22:54.613 に答える
48

カリー化は、関数に適用できる変換であり、以前よりも引数を 1 つ少なくすることができます。

たとえば、F# では次のように関数を定義できます。

let f x y z = x + y + z

ここで、関数 f はパラメーター x、y、z を取り、それらを合計します。

f 1 2 3

6 を返します。

したがって、定義から、f:- のカレー関数を定義できます。

let curry f = fun x -> f x

ここで、'fun x -> f x' は、C# の x => f(x) と同等のラムダ関数です。この関数は、カリー化したい関数を入力し、単一の引数を取り、最初の引数を入力引数に設定して指定された関数を返す関数を返します。

前の例を使用すると、次のように f のカレーを取得できます。

let curryf = curry f

その後、次のことができます。

let f1 = curryf 1

これにより、f1 yz = 1 + y + z と同等の関数 f1 が得られます。これは、次のことができることを意味します。

f1 2 3

これは 6 を返します。

このプロセスは、次のように定義できる「部分関数適用」と混同されることがよくあります。

let papply f x = f x

それを複数のパラメーターに拡張できますが、つまり:-

let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.

部分的なアプリケーションは、関数とパラメーターを受け取り、必要なパラメーターが 1 つ以上少ない関数を返します。前の 2 つの例が示すように、標準の F# 関数定義で直接実装されているため、前の結果を次のように実現できます。

let f1 = f 1
f1 2 3

これは 6 の結果を返します。

結論は:-

カリー化と部分関数適用の違いは次のとおりです。

カリー化は、関数を取り、単一の引数を受け入れる新しい関数を提供し、指定された関数の最初の引数をその引数に設定して返します。これにより、複数のパラメーターを持つ関数を一連の単一引数関数として表すことができます。例:-

let f x y z = x + y + z
let curryf = curry f
let f1 = curryf 1
let f2 = curryf 2
f1 2 3
6
f2 1 3
6

部分関数適用はより直接的です。関数と 1 つ以上の引数を取り、最初の n 個の引数が指定された n 個の引数に設定された関数を返します。例:-

let f x y z = x + y + z
let f1 = f 1
let f2 = f 2
f1 2 3
6
f2 1 3
6
于 2008-08-30T21:06:05.350 に答える
35

カリー化された関数は、最初の引数を受け入れ、2番目の引数を受け入れる関数を返すように書き直されたいくつかの引数の関数です。これにより、いくつかの引数の関数で、初期引数の一部を部分的に適用できます。

于 2008-10-19T05:35:02.467 に答える
10

カリー化とは、アリティ N の関数をアリティ 1 の N 関数に変換することです。arity関数の は、必要な引数の数です。

正式な定義は次のとおりです。

 curry(f) :: (a,b,c) -> f(a) -> f(b)-> f(c)

理にかなっている実際の例を次に示します。

お金を借りるためにATMに行きます。カードをスワイプし、暗証番号を入力して選択し、Enter キーを押して、リクエストと一緒に「金額」を送信します。

これは、お金を引き出すための通常の機能です。

const withdraw=(cardInfo,pinNumber,request){
    // process it
       return request.amount
}

この実装関数では、一度にすべての引数を入力する必要があります。カードをスワイプし、ピンを入力してリクエストを行うと、関数が実行されます。これらの手順のいずれかに問題があった場合は、すべての引数を入力した後にわかります。カリー化された関数を使用すると、アリティが高く、純粋で単純な関数を作成できます。純粋な関数は、コードを簡単にデバッグするのに役立ちます。

これはカリー化された関数を持つ Atm です:

const withdraw=(cardInfo)=>(pinNumber)=>(request)=>request.amount

ATM は、入力としてカードを受け取り、pinNumber を期待する関数を返します。この関数は、リクエスト オブジェクトを受け入れる関数を返します。プロセスが成功した後、リクエストした金額を取得します。各ステップでエラーが発生した場合、何が問題なのかを簡単に予測できます。カードを入力してエラーが発生したとしましょう。それはカードまたはマシンに関連しているが、ピン番号に関連していないことがわかっています。または、PIN を入力しても受け入れられない場合は、PIN 番号を間違って入力したことがわかります。エラーを簡単にデバッグできます。

また、ここにある各関数は再利用可能であるため、プロジェクトのさまざまな部分で同じ関数を使用できます。

于 2021-03-29T12:09:18.740 に答える
8

Python でのおもちゃの例を次に示します。

>>> from functools import partial as curry

>>> # Original function taking three parameters:
>>> def display_quote(who, subject, quote):
        print who, 'said regarding', subject + ':'
        print '"' + quote + '"'


>>> display_quote("hoohoo", "functional languages",
           "I like Erlang, not sure yet about Haskell.")
hoohoo said regarding functional languages:
"I like Erlang, not sure yet about Haskell."

>>> # Let's curry the function to get another that always quotes Alex...
>>> am_quote = curry(display_quote, "Alex Martelli")

>>> am_quote("currying", "As usual, wikipedia has a nice summary...")
Alex Martelli said regarding currying:
"As usual, wikipedia has a nice summary..."

(+ による連結を使用して、非 Python プログラマーの気を散らさないようにします。)

追加する編集:

http://docs.python.org/library/functools.html?highlight=partial#functools.partialを参照してください。これは、Python がこれを実装する方法における部分オブジェクトと関数の区別も示しています。

于 2009-08-30T17:47:50.960 に答える
5

これは、n no を使用した関数カリー化の一般的で最短のバージョンの例です。パラメータの。

const add = a => b => b ? add(a + b) : a; 

const add = a => b => b ? add(a + b) : a; 
console.log(add(1)(2)(3)(4)());

于 2019-10-08T16:20:45.460 に答える
5

理解できればpartial中途半端。のアイデアはpartial、引数を関数に事前に適用し、残りの引数のみを必要とする新しい関数を返すことです。この新しい関数が呼び出されると、プリロードされた引数と、それに提供された引数が含まれます。

Clojure+では関数ですが、明確にするために:

(defn add [a b] (+ a b))

incこの関数は、渡された数値に 1 を加算するだけであることに気付いているかもしれません。

(inc 7) # => 8

を使用して自分で構築しましょうpartial

(def inc (partial add 1))

ここでは、 の最初の引数に 1 がロードされた別の関数を返しますaddadd2 つの引数を取るため、新しいinc関数は引数のみを必要とします。1 つbは既に部分的に適用されているため、以前のように 2 つの引数ではありません。したがってpartial、事前に提供されたデフォルト値を使用して新しい関数を作成するためのツールです。そのため、関数型言語では、関数の引数が一般的なものから特定のものへと順序付けられることがよくあります。これにより、そのような関数を再利用して他の関数を構築することが容易になります。

addこの言語が、 2 つの引数が必要であることを内省的に理解できるほど賢いかどうかを想像してみてください。ためらうのではなく 1 つの引数を渡したときに、関数が引数を部分的に適用した場合、後でもう一方の引数を提供するつもりであると理解して、代わりに渡した場合はどうなるでしょうか? incを明示的に使用せずに定義できますpartial

(def inc (add 1)) #partial is implied

これは、いくつかの言語の振る舞いです。関数をより大きな変換に構成したい場合、これは非常に便利です。これはトランスデューサにつながります。

于 2015-06-26T20:26:56.353 に答える
4

Curry はコードを単純化できます。これは、これを使用する主な理由の 1 つです。カリー化とは、n 個の引数を受け入れる関数を、1 つの引数のみを受け入れる n 個の関数に変換するプロセスです。

原則として、渡された関数の引数をクロージャー (closure) プロパティを使用して渡し、それらを別の関数に格納して戻り値として扱い、これらの関数がチェーンを形成し、最終的な引数が渡されて完了します。操作。

これの利点は、一度に 1 つのパラメーターを処理することでパラメーターの処理を単純化できることです。これにより、プログラムの柔軟性と可読性も向上します。これにより、プログラムの管理も容易になります。また、コードをより小さな部分に分割すると、再利用しやすくなります。

例えば:

function curryMinus(x) 
{
  return function(y) 
  {
    return x - y;
  }
}

var minus5 = curryMinus(1);
minus5(3);
minus5(5);

私もできる...

var minus7 = curryMinus(7);
minus7(3);
minus7(5);

これは、複雑なコードをきれいに作成したり、非同期メソッドを処理したりするのに非常に優れています。

于 2018-06-21T06:49:22.367 に答える
3

この記事と、それが参照する記事は、カリー化をよりよく理解するのに役立ちます

他の人が述べたように、これは 1 つのパラメーター関数を持つ方法にすぎません。

これは、渡されるパラメーターの数を想定する必要がないため、2 パラメーター、3 パラメーター、および 4 パラメーター関数を必要としないという点で便利です。

于 2009-08-30T02:19:59.127 に答える
2

カリー化された関数は、1 つだけではなく、複数の引数リストに適用されます。

以下は、x と y の 2 つの Int パラメータを追加する通常の非カリー化関数です。

scala> def plainOldSum(x: Int, y: Int) = x + y
plainOldSum: (x: Int,y: Int)Int
scala> plainOldSum(1, 2)
res4: Int = 3

これは、カリー化された同様の関数です。2 つの Int パラメーターの 1 つのリストの代わりに、この関数をそれぞれ 1 つの Int パラメーターの 2 つのリストに適用します。

scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Intscala> second(2)
res6: Int = 3
scala> curriedSum(1)(2)
res5: Int = 3

ここで起こっていることは、 を呼び出すcurriedSumと、実際には 2 つの従来の関数呼び出しが連続して行われるということです。最初の関数呼び出しは、 という名前の単一の Int パラメータを取りx、2 番目の関数の関数値を返します。この 2 番目の関数は、Int パラメータを取ります y

firstこれは、最初の伝統的な関数呼び出しが行うことを精神的に行うという名前の関数curriedSumです。

scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int

最初の関数に 1 を適用すると、つまり、最初の関数を呼び出して 1 を渡すと、2 番目の関数が生成されます。

scala> val second = first(1)
second: (Int) => Int = <function1>

2 を 2 番目の関数に適用すると、次の結果が得られます。

scala> second(2)
res6: Int = 3
于 2014-05-04T09:03:57.520 に答える
2

カリー化の例は、現時点でパラメーターの 1 つしか知らない関数がある場合です。

例えば:

func aFunction(str: String) {
    let callback = callback(str) // signature now is `NSData -> ()`
    performAsyncRequest(callback)
}

func callback(str: String, data: NSData) {
    // Callback code
}

func performAsyncRequest(callback: NSData -> ()) {
    // Async code that will call callback with NSData as parameter
}

ここでは、コールバックを送信するときに 2 番目のパラメーターがわからないため、performAsyncRequest(_:)別のラムダ/クロージャーを作成して関数に送信する必要があります。

于 2016-04-11T07:03:26.950 に答える
1

「カリー化」とは、複数の引数の関数を取得し、それぞれが単一の引数を取り、単一の引数の関数を返す、または最終的な関数の場合は実際の結果を返す一連の関数に変換するプロセスです。

于 2020-11-28T10:54:09.133 に答える