カレーの利点は何ですか?


154

私はカレーについて学んだばかりで、コンセプトを理解しているとは思いますが、それを使用することに大きな利点はありません。

些細な例として、2つの値を追加する関数を使用します(MLで記述)。カレーなしのバージョンは

fun add(x, y) = x + y

と呼ばれます

add(3, 5)

カレー版は

fun add x y = x + y 
(* short for val add = fn x => fn y=> x + y *)

と呼ばれます

add 3 5

私は、関数の定義と呼び出しから括弧のセットを削除する構文糖衣のように思えます。カリー化は関数型言語の重要な機能の1つとして挙げられてきましたが、現時点ではそれに少々困惑しています。タプルをとる関数の代わりに、それぞれ単一のパラメーターを消費する関数のチェーンを作成するという概念は、単純な構文の変更に使用するのはかなり複雑に思えます。

わずかに単純な構文がカリー化の唯一の動機ですか、それとも私の非常に単純な例では明らかではない他の利点がありませんか?カレーは単なる構文糖ですか?


54
カリー化だけでは本質的には役に立ちませんが、デフォルトですべての関数をカリー化することで、他の多くの機能をより使いやすくすることができます。しばらく関数型言語を実際に使用するまで、これを理解することは困難です。
CAマッキャン

4
ジョエル・イーサートンの答えのコメントでデルナンを渡す際に言及されたが、私が明示的に言及すると思ったのは、(少なくともHaskellでは)関数だけでなく型コンストラクタでも部分的に適用できることです-これはかなり可能ですハンディ; これは考えるべきことかもしれません。
ポール

すべてがHaskellの例を示しています。カレーはHaskellでのみ役立つのではないかと思うかもしれません。
マノジR

@ManojRすべてがHaskellで例を挙げていません
phwd

1
この質問はRedditについてかなり興味深い議論を生み出しました。
ヤニス

回答:


126

カリー化された関数を使用すると、特殊化できるため、より抽象的な関数を簡単に再利用できます。追加機能があるとしましょう

add x y = x + y

そして、リストのすべてのメンバーに2を追加する必要があります。Haskellではこれを行います:

map (add 2) [1, 2, 3] -- gives [3, 4, 5]
-- actually one could just do: map (2+) [1, 2, 3], but that may be Haskell specific

ここでは、関数を作成する必要がある場合よりも構文が軽い add2

add2 y = add 2 y
map add2 [1, 2, 3]

または、匿名のラムダ関数を作成する必要がある場合:

map (\y -> 2 + y) [1, 2, 3]

また、異なる実装から抽象化することもできます。2つのルックアップ関数があったとしましょう。次のように、キー/値のペアのリストとキーから値へ、およびマップからのキーから値へ、キーから値へ

lookup1 :: [(Key, Value)] -> Key -> Value -- or perhaps it should be Maybe Value
lookup2 :: Map Key Value -> Key -> Value

次に、キーから値へのルックアップ関数を受け入れる関数を作成できます。上記のルックアップ関数のいずれかを渡すことができ、それぞれリストまたはマップのいずれかで部分的に適用されます:

myFunc :: (Key -> Value) -> .....

結論として、カリー化は優れています。軽量構文を使用して関数を特殊化/部分的に適用し、これらの部分的に適用された関数をmapor などの高次関数に渡すことができるからですfilter。高次関数(関数をパラメーターとして使用するか、結果として生成する)は関数型プログラミングの基本であり、カリー化および部分的に適用された関数により、高次関数をより効果的かつ簡潔に使用できます。


31
このため、Haskellの関数に使用される引数の順序は、多くの場合、部分的なアプリケーションの可能性に基づいているため、上記の利点が(ha、ha)より多くの状況で適用されることに注意してください。したがって、デフォルトのカリー化は、ここにあるような特定の例から明らかなよりもさらに有益です。
CAマッキャン

ワット。「キー/値ペアのリストと値のキーと別のマップからキーから値へと値をキーからワン」
Mateen Ulhaq

@MateenUlhaqこれは前の文の続きです。ここでは、キーに基づいて値を取得することを想定しており、2つの方法があります。文はこれらの2つの方法を列挙します。最初の方法では、キーと値のペアのリストと値を検索するキーが提供され、別の方法では適切なマップが提供され、再びキーが提供されます。文の直後のコードを見ると役立つ場合があります。
ボリス

53

実際的な答えは、カリー化により匿名関数の作成がはるかに簡単になるということです。最小限のラムダ構文でさえ、それは何かの勝利です。比較する:

map (add 1) [1..10]
map (\ x -> add 1 x) [1..10]

見苦しいラムダ構文がある場合は、さらにひどいです。(私はあなた、JavaScript、Scheme、Pythonを見ています。)

これは、ますます高階関数を使用するにつれて有用になります。私が使用していますが、より他の言語に比べてHaskellで高階関数を、私は私が実際にラムダ構文を使用して見つけた以下の時間の三分の二のようなものは、ラムダはただ部分的に適用された機能になるので。(それ以外の場合は、名前付き関数に抽出します。)

より基本的には、どのバージョンの関数が「標準」であるかが常に明らかであるとは限りません。例えば、取りますmap。タイプはmap2つの方法で記述できます。

map :: (a -> b) -> [a] -> [b]
map :: (a -> b) -> ([a] -> [b])

「正しい」ものはどれですか?実際に言うのは難しいです。実際には、ほとんどの言語は最初のものを使用します。マップは関数とリストを取り、リストを返します。ただし、基本的に、mapが実際に行うのは、通常の関数をリスト関数にマップすることです。関数を受け取り、関数を返します。マップがカリー化されている場合、この質問に答える必要はありません。非常にエレガントな方法で両方を行います。

mapリスト以外の型に一般化すると、これは特に重要になります。

また、カレーは本当に複雑ではありません。実際には、ほとんどの言語が使用するモデルを少し単純化したものです。言語に焼き付けられた複数の引数の関数の概念は必要ありません。これは、基になるラムダ計算をより密接に反映しています。

もちろん、MLスタイルの言語には、カリー形式またはカリー形式ではない複数の引数の概念はありません。f(a, b, c)構文は、実際にタプルを渡すに対応(a, b, c)f、とてもfまだのみの引数を取ります。これは、次のようなものを書くことが非常に自然になるので、他の言語が持っていることを望む非常に便利な区別です。

map f [(1,2,3), (4,5,6), (7, 8, 9)]

複数の引数が組み込まれているという考えを持つ言語では、これを簡単に行うことはできませんでした!


1
「MLスタイルの言語には、カリー形式またはカリー形式ではない複数の引数の概念はありません」:この点で、Haskell MLスタイルですか?
ジョルジオ

1
@ジョルジョ:ええ。
ティコンジャービス

1
面白い。Haskellを知っていて、今SMLを学んでいるので、2つの言語の違いと類似点を見るのは面白いです。
ジョルジオ

偉大な答えは、あなたはまだ納得していないなら、単にラムダストリームに似ているUnixのパイプラインについて考える
シュリダールSarnobat

「実用的な」答えはあまり重要ではありません。なぜなら、冗長性は通常、カリーではなく部分的な適用によって回避されるからです。そして、ここでは、ラムダ抽象化の構文は(型宣言にもかかわらず)Schemeの構文よりもsyntaxい(少なくとも)、それを正しく解析するにはより多くの組み込みの特別な構文規則が必要であり、それは言語仕様を利益なしに肥大化させるセマンティックプロパティについて。
FrankHB

24

カリー化は、ファーストクラスオブジェクトとして渡している関数があり、それを評価するために必要なすべてのパラメーターをコードの1か所で受け取れない場合に役立ちます。1つまたは複数のパラメーターを取得して適用し、その結果を、より多くのパラメーターを持つ別のコードに渡して、そこで評価を終了することができます。

これを実現するコードは、最初にすべてのパラメーターを一緒に取得する必要がある場合よりも簡単になります。

また、単一のパラメーター(別のカリー化された関数)をとる関数は、すべてのパラメーターと明確に一致する必要がないため、コードを再利用する可能性があります。


14

カリー化の主な動機(少なくとも最初は)は実用的ではなく、理論的でした。特に、カリー化により、実際にセマンティクスを定義したり、製品のセマンティクスを定義したりすることなく、複数の引数を持つ関数を効果的に取得できます。これにより、他のより複雑な言語と同じくらい表現力のあるよりシンプルな言語が得られるため、望ましい言語です。


2
ここでの動機は理論的ですが、単純さはほとんど常に実用的な利点でもあると思います。複数の引数を持つ関数を気にしないと、セマンティクスを使用していた場合と同様に、プログラミングするときに私の人生が楽になります。
ティコンジャービス

2
@TikhonJelvisただし、プログラミングしているとき、カリー化は、コンパイラーが関数に渡した引数が少なすぎるという事実をキャッチしなかったり、その場合に悪いエラーメッセージを取得したりするなど、心配する必要があります。カリー化を使用しない場合、エラーはより明確になります。
アレックスR

私はそのような問題を抱えたことは一度もありません。GHCは、少なくともその点では非常に優れています。コンパイラは常にこの種の問題をキャッチし、このエラーに対しても適切なエラーメッセージを提供します。
ティコンジェルビス

1
エラーメッセージが良好と見なされることに同意できません。はい、サービス可能ですが、まだ良くありません。また、型エラーが発生した場合、つまり、後で関数以外の結果として使用しようとした場合(または、注釈を入力したが、読み取り可能なエラーをそれに依存している場合)、その種の問題のみをキャッチします); エラーの報告された場所は、実際の場所と離婚しています。
アレックスR

14

(Haskellで例を挙げます。)

  1. 関数型言語を使用する場合、関数を部分的に適用できると非常に便利です。Haskellのように、引数が与えられた項と等しい場合に(== x)返される関数です:Truex

    mem :: Eq a => a -> [a] -> Bool
    mem x lst = any (== x) lst
    

    カリー化せずに、やや読みにくいコードになるでしょう:

    mem x lst = any (\y -> y == x) lst
    
  2. これは暗黙のプログラミングに関連しています(Haskell wikiのPointfreeスタイルも参照)。このスタイルは、変数で表される値ではなく、関数の構成と情報が関数のチェーンを流れる方法に焦点を当てています。サンプルを、変数をまったく使用しないフォームに変換できます。

    mem = any . (==)
    

    ここでは、ビュー==からの関数としてaa -> Boolanyから関数としてa -> Bool[a] -> Bool。単純に構成するだけで、結果が得られます。これはすべてカレーのおかげです。

  3. 逆の非カリー化も、状況によっては便利です。たとえば、リストを2つの部分(10より小さい要素と残りの要素)に分割し、それらの2つのリストを連結するとします。リストの分割は(ここでもcurriedを使用します)によって行われます。結果の型はです。結果を最初と2番目の部分に抽出し、を使用してそれらを結合する代わりに、partition (< 10)<([Int],[Int])++++

    uncurry (++) . partition (< 10)
    

実際、に(uncurry (++) . partition (< 10)) [4,12,11,1]評価され[4,1,12,11]ます。

重要な理論上の利点もあります。

  1. カリー化は、データ型がなく、ラムダ計算などの機能のみを持つ言語にとって不可欠です。これらの言語は実際の使用には役立ちませんが、理論的な観点からは非常に重要です。
  2. これは、関数型言語の本質的な特性に関連しています-関数はファーストクラスのオブジェクトです。これまで見てきたように、から(a, b) -> cへの変換a -> (b -> c)は、後者の関数の結果がtypeであることを意味しb -> cます。つまり、結果は関数です。
  3. (Un)curryingは、デカルトの閉じたカテゴリに密接に関連しています。これは、型付きラムダ計算をカテゴリカルに表示する方法です。

「はるかに読みにくいコード」ビットについては、そうではないmem x lst = any (\y -> y == x) lstでしょうか?(バックスラッシュ付き)。
stusmith

はい、それを指摘してくれてありがとう、私はそれを修正します。
ペトルスキー-パドラック

9

カリー化は単なる構文糖ではありません!

add1(uncurried)とadd2(curried)の型シグネチャを考慮してください:

add1 : (int * int) -> int
add2 : int -> (int -> int)

(どちらの場合も、型シグネチャの括弧はオプションですが、わかりやすくするために括弧を含めています。)

add1の2-タプルをとる関数であるintintと戻りますintadd2とる関数であるintと返す別の関数順番に取るintと返しますがint

関数適用を明示的に指定すると、この2つの本質的な違いがより明確になります。最初の引数を2番目の引数に適用する関数(カリー化されていない)を定義しましょう。

apply(f, b) = f b

これでadd1add2より明確に違いを見ることができます。 add12タプルで呼び出されます:

apply(add1, (3, 5))

しかしadd2、で呼び出され、int その戻り値は別で呼び出されますint

apply(apply(add2, 3), 5)

編集: カリー化の本質的な利点は、部分的なアプリケーションを無料で入手できることです。パラメータに5を追加したタイプの関数int -> intmapリストなど)が必要だとしましょう。あなたは書くことができますaddFiveToParam x = x+5、またはインラインラムダで同等のことをすることができますが、もっと簡単に(特にこれよりも些細ではない場合)書くこともできますadd2 5


3
私の例では舞台裏に大きな違いがあることを理解していますが、結果は単純な構文の変更のようです。
マッドサイエンティスト

5
カリー化は非常に深い概念ではありません。基礎となるモデル(Lambda Calculusを参照)を単純化するか、タプルを含む言語で実際に部分的なアプリケーションの構文の利便性についてです。構文上の利便性の重要性を過小評価しないでください。
ピーク

9

カリー化は単なる構文糖ですが、糖が何をしているのか少し誤解していると思います。あなたの例を挙げると、

fun add x y = x + y

実際には構文上の砂糖です

fun add x = fn y => x + y

つまり、(add x)は引数yをとる関数を返し、xをyに追加します。

fun addTuple (x, y) = x + y

それは、タプルを取り、その要素を追加する関数です。これらの2つの関数は実際にはかなり異なります。それらは異なる引数を取ります。

リスト内のすべての数値に2を追加する場合:

(* add 2 to all numbers using the uncurried function *)
map (fn x => addTuple (x, 2)) [1,2,3]
(* using the curried function *)
map (add 2) [1,2,3]

結果はになります[3,4,5]

一方、リスト内の各タプルを合計する場合、addTuple関数は完全に適合します。

(* Sum each tuple using the uncurried function *)
map addTuple [(10,2), (10,3), (10,4)]    
(* sum each tuple using curried function *)
map (fn (a,b) => add a b) [(10,2), (10,3), (10,4)]

結果はになります[12,13,14]

カリー化された関数は、部分的なアプリケーション(マップ、折り畳み、アプリ、フィルターなど)が役立つ場合に最適です。指定されたリストで最大の正の数を返すこの関数を考えます。正の数がない場合は0を返します。

- val highestPositive = foldr Int.max 0;   
val highestPositive = fn : int list -> int 

1
カリー化された関数には異なる型シグネチャがあり、実際には別の関数を返す関数であることを理解しました。ただし、部分的なアプリケーション部分が欠落していました。
マッドサイエンティスト

9

私がまだ言及していないもう一つのことは、カリー化がアリティよりも(制限された)抽象化を可能にするということです。

Haskellのライブラリの一部であるこれらの関数を検討してください

(.) :: (b -> c) -> (a -> b) -> a -> c
either :: (a -> c) -> (b -> c) -> Either a b -> c
flip :: (a -> b -> c) -> b -> a -> c
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c

いずれの場合も、型変数cは関数型にすることができるため、これらの関数は引数のパラメーターリストのプレフィックスで機能します。カリー化せずに、関数アリティを抽象化するための特別な言語機能が必要か、異なるアリティに特化したこれらの関数の多くの異なるバージョンが必要です。


6

私の限られた理解は次のとおりです:

1)部分関数アプリケーション

部分関数アプリケーションは、引数の数が少ない関数を返すプロセスです。3つの引数のうち2つを指定すると、3-2 = 1の引数を取る関数が返されます。3つの引数のうち1つを指定すると、3-1 = 2引数を取る関数が返されます。必要に応じて、3つの引数のうち3つを部分的に適用することもでき、引数をとらない関数を返します。

したがって、次の関数が与えられます:

f(x,y,z) = x + y + z;

1をxにバインドし、それを上記の関数に部分的に適用すると、次のようにf(x,y,z)なります:

f(1,y,z) = f'(y,z);

どこ: f'(y,z) = 1 + y + z;

yを2にバインドし、zを3にバインドし、部分的に適用f'(y,z)すると、次のようになります。

f'(2,3) = f''();

どこに:f''() = 1 + 2 + 3;

これで、任意の時点でff'またはの評価を選択できますf''。だから私はできる:

print(f''()) // and it would return 6;

または

print(f'(1,1)) // and it would return 3;

2)カレー

一方、カリー化は、関数を1つの引数関数のネストされたチェーンに分割するプロセスです。2つ以上の引数を指定することはできません。1つまたは0です。

したがって、同じ関数を与えられます:

f(x,y,z) = x + y + z;

カリー化すると、3つの関数のチェーンが得られます。

f'(x) -> f''(y) -> f'''(z)

どこ:

f'(x) = x + f''(y);

f''(y) = y + f'''(z);

f'''(z) = z;

お電話の場合は今すぐf'(x)x = 1

f'(1) = 1 + f''(y);

新しい関数が返されます:

g(y) = 1 + f''(y);

あなたが呼び出す場合g(y)y = 2

g(2) = 1 + 2 + f'''(z);

新しい関数が返されます:

h(z) = 1 + 2 + f'''(z);

最後に、あなたが呼び出す場合h(z)z = 3

h(3) = 1 + 2 + 3;

あなたが返され6ます。

3)閉鎖

最後に、クロージャーとは、関数とデータを単一のユニットとして一緒に取り込むプロセスです。関数クロージャは、0から無限の数の引数を取ることができますが、渡されないデータも認識します。

繰り返しますが、同じ関数が与えられた場合:

f(x,y,z) = x + y + z;

代わりにクロージャーを書くことができます:

f(x) = x + f'(y, z);

どこ:

f'(y,z) = x + y + z;

f'が閉鎖されていxます。f'内部にあるxの値を読み取ることができるという意味f

だからあなたが電話fする場合x = 1

f(1) = 1 + f'(y, z);

あなたは閉鎖を得ます:

closureOfF(y, z) =
                   var x = 1;
                   f'(y, z);

ここclosureOfFy = 2andで呼び出した場合z = 3

closureOfF(2, 3) = 
                   var x = 1;
                   x + 2 + 3;

どちらが返されます 6

結論

カリー化、部分適用、およびクロージャーはすべて、機能をより多くの部分に分解するという点で多少似ています。

カリー化は、複数の引数の関数を、単一の引数の関数を返す単一の引数のネストされた関数に分解します。1つ以下の引数の関数をカリー化しても意味がありません。

部分的なアプリケーションは、複数の引数の関数をより小さな引数の関数に分解し、現在欠落している引数が指定された値に置き換えられます。

クロージャーは、関数を関数とデータセットに分解し、関数内の渡されなかった変数がデータセット内を調べて、評価を求められたときにバインドする値を見つけることができます。

これらすべてについて混乱しているのは、他のサブセットを実装するためにそれぞれを使用できることです。そのため、本質的に、それらはすべて実装の詳細のビットです。すべての値を事前に収集する必要がなく、関数の一部を個別の単位に分解しているため、関数の一部を再利用できるという点で、これらはすべて同じ値を提供します。

開示

私は決してこのトピックの専門家ではありません。最近これらのことを学び始めたばかりなので、現在の理解を提供しますが、間違いがあるかもしれませんので、指摘するように勧めます。発見します。


1
だから答えは次のとおりです。カレーは利点がありませんか?
ceving

1
@ceving私の知る限り、それは正しいです。実際には、カレーと部分適用は同じ利点をもたらします。言語に実装する選択は、実装上の理由で行われます。特定の言語を指定すると、別の言語よりも簡単に実装できる場合があります。
ディディエA.

5

カリー化(部分アプリケーション)を使用すると、いくつかのパラメーターを修正することにより、既存の関数から新しい関数を作成できます。これは、匿名関数がキャプチャされた引数を別の関数に渡す単純なラッパーであるレキシカルクロージャの特殊なケースです。また、字句クロージャーを作成するための一般的な構文を使用してこれを行うこともできますが、部分的なアプリケーションは単純化された構文糖を提供します。

これが、Lispプログラマーが機能的なスタイルで作業するときに、部分的なアプリケーションにライブラリーを使用する場合がある理由です

(lambda (x) (+ 3 x))引数に3を追加する関数を提供するの代わりに、などのよう(op + 3)に記述できます。したがって、someのリストのすべての要素に3を追加するには、(mapcar (op + 3) some-list)ではなくになり(mapcar (lambda (x) (+ 3 x)) some-list)ます。このopマクロは、いくつかの引数を取りx y z ...、呼び出す関数を作成します(+ a x y z ...)

多くの純粋に機能的な言語では、部分的なアプリケーションが構文に染み込んでいるため、op演算子はありません。部分的なアプリケーションをトリガーするには、必要な引数よりも少ない引数で関数を呼び出すだけです。"insufficient number of arguments"エラーを生成する代わりに、結果は残りの引数の関数です。


「Currying ...では、いくつかのパラメーターを修正して、新しい関数を作成できます」-いいえ、タイプの関数にa -> b -> cはパラメーターs(複数)はなく、パラメーターは1つだけcです。呼び出されると、typeの関数を返しますa -> b
マックスハイバー

4

機能について

fun add(x, y) = x + y

それは形です f': 'a * 'b -> 'c

評価するには

add(3, 5)
val it = 8 : int

カレー機能用

fun add x y = x + y

評価するには

add 3
val it = fn : int -> int

部分的な計算、具体的には(3 + y)の場合、次のように計算を完了できます。

it 5
val it = 8 : int

2番目のケースで追加する形式は f: 'a -> 'b -> 'c

ここでカリー化することは、2つの合意をとる関数を、結果を返す1つの合意のみをとる関数に変換することです。部分評価

なぜこれが必要なのでしょうか?

xRHSで言うと、単なる通常のintではなく、2秒の拡張のために完了するまでに時間がかかる複雑な計算です。

x = twoSecondsComputation(z)

したがって、関数は次のようになります

fun add (z:int) (y:int) : int =
    let
        val x = twoSecondsComputation(z)
    in
        x + y
    end;

タイプの add : int * int -> int

次に、ある範囲の数値に対してこの関数を計算したいので、それをマッピングしましょう

val result1 = map (fn x => add (20, x)) [3, 5, 7];

上記の場合、結果twoSecondsComputationは毎回評価されます。つまり、この計算には6秒かかります。

ステージングとカレーの組み合わせを使用すると、これを回避できます。

fun add (z:int) : int -> int =
    let
        val x = twoSecondsComputation(z)
    in
        (fn y => x + y)
    end;

カレーの形の add : int -> int -> int

できるようになりました

val add' = add 20;
val result2 = map add' [3, 5, 7, 11, 13];

twoSecondsComputation一度だけ評価される必要があります。スケールを上げるには、2秒を15分または1時間に置き換えてから、100個の数字に対するマップを作成します。

要約:カリー化は、部分評価のツールとして、より高いレベルの機能のために他の方法を使用する場合に最適です。その目的は、それ自体では実際に実証できません。


3

カリー化により、柔軟な機能構成が可能になります。

「カレー」関数を作成しました。この文脈では、どのような種類のロガーを取得するのか、それがどこから来たのかは気にしません。アクションが何であるか、またはどこから来たのかは気にしません。私が気にするのは、入力を処理することだけです。

var builder = curry(function(input, logger, action) {
     logger.log("Starting action");
     try {
         action(input);
         logger.log("Success!");
     }
     catch (err) {
         logger.logerror("Boo we failed..", err);
     }
});
var x = "My input.";
goGatherArgs(builder)(x); // Supplies action first, then logger somewhere.

ビルダー変数は、作業を行う入力を受け取る関数を返す関数を返す関数です。これは単純な便利な例であり、視界にあるオブジェクトではありません。


2

カリー化は、関数のすべての引数を持っていない場合の利点です。関数を完全に評価している場合、大きな違いはありません。

カリー化により、まだ必要のないパラメーターに言及するのを避けることができます。より簡潔で、スコープ内の別の変数と衝突しないパラメーター名を見つける必要はありません(これは私のお気に入りの利点です)。

たとえば、関数を引数として使用する関数を使用する場合、「入力に3を追加」や「入力を変数vと比較」などの関数が必要な場合がよくあります。カリー化と、これらの機能を簡単に書き込まれますadd 3(== v)。カリー化せずに、ラムダ式を使用する必要がx => add 3 xありx => x == vます:と。ラムダ式は2倍の長さで、x既にxスコープ内にある場合に加えて、名前の選択に関連する少量の忙しい作業があります。

カリー化に基づく言語の副次的な利点は、関数の汎用コードを作成するときに、パラメーターの数に基づいて数百のバリアントが作成されないことです。たとえば、C#では、「curry」メソッドにはFunc <R>、Func <A、R>、Func <A1、A2、R>、Func <A1、A2、A3、R>などのバリアントが必要です。永遠に。Haskellでは、Func <A1、A2、R>に相当するものは、Func <Tuple <A1、A2>、R>またはFunc <A1、Func <A2、R >>(およびFunc <R> Func <Unit、R>)に近いため、すべてのバリアントはFunc <A、R>の単一のケースに対応します。


2

私が考えることができる主要な推論(そして、私は決してこの主題の専門家ではない)は、機能が自明ではないものから自明でないものに移行するにつれてその利点を示し始めます。この性質のほとんどの概念を備えた些細なケースでは、本当の利点はありません。ただし、ほとんどの関数型言語は、処理操作でスタックを多用します。この例として、PostScriptまたはLispを検討してください。カリー化を利用することにより、機能をより効果的に積み重ねることができ、この利点は操作が次第に大きくなるにつれて明らかになります。カリー化された方法で、コマンドと引数は順番にスタックにスローされ、必要に応じてポップオフされるため、適切な順序で実行されます。


1
より多くのスタックフレームを作成する必要があると、物事はより効率的になりますか?
メイソンウィーラー

1
@MasonWheeler:私は、関数型言語やカレーの専門家ではないと言ったので、知りません。そのため、このコミュニティWikiにラベルを付けました。
ジョエルイーサートン

4
@MasonWheelerこの答えの言い回しについてはポイントがありますが、実際に作成されるスタックフレームの量は実装に大きく依存すると言います。たとえば、スパインレスタグレスGマシン(STG; GHCによるHaskellの実装方法)では、すべての(または少なくとも必要なことがわかっている数の)引数を蓄積するまで実際の評価を遅らせます。私は、これはすべての機能にのみコンストラクタのために行われているかどうかを思い出すように見えることはできませんが、私は考えて、それはほとんどの機能のために可能であるべき。(再度、「スタックフレーム」の概念はSTGに実際には適用されません。)

1

カリー化は、関数を返す能力に決定的に(決定的にさえ)依存しています。

この(工夫された)擬似コードを検討してください。

var f =(m、x、b)=> ...何かを返す...

3つ未満の引数でfを呼び出すと、関数が返されると規定しましょう。

var g = f(0、1); //これは、もう1つの引数(b)を受け入れる0と1(mとx)にバインドされた関数を返します。

var y = g(42); // mとxに0と1を使用して、欠落している3番目の引数でgを呼び出します

引数を部分的に適用し、再利用可能な関数(指定した引数にバインドされている)を取得できることは非常に便利です(DRY)。

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