2008-08-12 15 views

答えて

19

(編集:物事を始めるために小さなOcaml FP Koan

カリー化の洪庵(食品について公案、それは食べ物ではないです)

学生が来ましたJacques Garrigueに電話し、「カレーが良いものは何か分かりません」と言った。ジャックは "あなたの好きな食事とお好みのデザートを教えてください"と答えました。困った学生は、お好み焼きやカントーが好きだと答えたが、大好きなお好み焼きを食べていたのに、翌朝、カントンがいつも腹痛を与えた。そこで、ジャックは生徒に、お好み焼きを提供していたレストランで食べ物を食べ、学生の好みと同じくらい食べました。生徒は出産したが、覚醒していなかった...翌朝、目が覚めて胃がうまく感じられた。

私の例では、コードの再利用およびカプセル化のためにそれを使用してカバーします。一度これを見ると、これはかなり明白であり、多くの状況で適用すると考えることができる具体的で簡単な例を示すはずです。

マップをツリー上に作成したいと考えています。この関数はカーリー化され、複数の引数が必要な場合は各ノードに適用できます。ノードの最後の引数として適用するためです。カレットする必要はありませんが、の別の関数(この関数は他の変数で他のインスタンスで使用されていると仮定します)を作成するのは無駄です。

type 'a tree = E of 'a | N of 'a * 'a tree * 'a tree 
let rec tree_map f tree = match tree with 
    | N(x,left,right) -> N(f x, tree_map f left, tree_map f right) 
    | E(x) -> E(f x) 

let sample_tree = N(1,E(3),E(4) 
let multiply x y = x * y 
let sample_tree2 = tree_map (multiply 3) sample_tree 

が、これは同じです。だから、この単純なケースは説得力がない

let sample_tree2 = tree_map (fun x -> x * 3) sample_tree 

。一度あなたが言語をもっと使い、これらの状況に自然に遭遇すれば、本当にそうです。他の例では、いくつかのコードをカリングとして再利用しています。 A recurrence relation to create prime numbers。そこでの類似性の非常に多く:

let rec f_recurrence f a seed n = 
    match n with 
    | a -> seed 
    | _ -> let prev = f_recurrence f a seed (n-1) in 
      prev + (f n prev) 

let rowland = f_recurrence gcd 1 7 
let cloitre = f_recurrence lcm 1 1 

let rowland_prime n = (rowland (n+1)) - (rowland n) 
let cloitre_prime n = ((cloitre (n+1))/(cloitre n)) - 1 

[OK]を、今、ローランドとクロワトルは、彼らが自由変数を持っているので、関数をカリー化されている、と我々はそれの任意のインデックスが知っているかf_recurrenceを気にせずに、シーケンスの取得することができます。

+3

(http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog

2

これはかなり簡単なプロセスです。関数を引数に取り、新しい関数を返します。たとえば、次のように簡単なconcatStrings関数をカリー化することによって今

let concatStrings left right = left + right 
let makeCommandPrompt= appendString "c:\> " 

、あなたは簡単に任意の文字列の前にDOS形式のコマンド・プロンプトを追加することができます!本当に便利です!

さて、実際はありません。私が見つけたより有用なケースは、私がストリームのような方法で私のデータを返す関数を作るようにしたいときです。

let readDWORD array i = array[i] | array[i + 1] << 8 | array[i + 2] << 16 | 
    array[i + 3] << 24 //I've actually used this function in Python. 

それについての便利な部分はobj.readDWORD()を呼び出して、コンストラクタを呼び出して、むしろ、この種のもののために全体のクラスを作成するよりも、つまり、あなただけから出て突然変異させることができない機能を持っていますあなたの下に

+2

この回答は部分的な機能のアプリケーションについて記述しています。(これはカリングに関連していますが、同じものではありません。)http://ja.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application – phoog

14

上記の例では質問に答えましたが、ここではCurryingがF#プログラミングにどのように役立つかという2つの簡単な例があります。

open System.IO 

let appendFile (fileName : string) (text : string) = 
    let file = new StreamWriter(fileName, true) 
    file.WriteLine(text) 
    file.Close() 

// Call it normally  
appendFile @"D:\Log.txt" "Processing Event X..." 

// If you curry the function, you don't need to keep specifying the 
// log file name. 
let curriedAppendFile = appendFile @"D:\Log.txt" 

// Adds data to "Log.txt" 
curriedAppendFile "Processing Event Y..." 

また、Printfファミリーの機能をカレーすることもできます。カレーされたバージョンでは、ラムダの明確な欠如に気づくでしょう。

// Non curried, Prints 1 2 3 
List.iter (fun i -> printf "%d " i) [1 .. 3];; 

// Curried, Prints 1 2 3 
List.iter (printfn "%d ") [1 .. 3];; 
+3

この回答は、部分的な機能の適用について説明しています。[これはカレーに関連していますが、同じものではありません] http://ja.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog

2

あなたは関数をリストにマップできますか?例えば、リストの各要素に1を追加する機能をマッピング:(+)演算子は、その引数に1を追加する関数を作成するために使用されたが、あなたは絞ることができるので、

> List.map ((+) 1) [1; 2; 3];; 
val it : int list = [2; 3; 4] 

これは、カリー化を使用して、すでに実際にありますあなたが部分的にこれらの関数を適用することができませんでしたし、代わりにこのような何かを書かなければならないだろうカリー化せずに

> List.map (List.map ((+) 1)) [[1; 2]; [3]];; 
val it : int list = [[2; 3]; [4]] 

> List.map((fun xs -> List.map((fun n -> n + 1), xs)), [[1; 2]; [3]]);; 
val it : int list = [[2; 3]; [4]] 
リストのリストの同じ機能をマッピングするために、それを変更することにより、この例のうち、もう少し
+2

この回答は、部分的な機能のアプリケーションについて説明しています。[これはカリングに関連していますが、同じものではありません](http://en.wikipedia.org/wiki/ Currying#Contrast_with_partial_function_application) – phoog

+0

@phoogこの回答は正しく解説されています「カールすることなく、これらの機能を部分的に適用することはできませんでした」。 –

1

私はC#on my blogでカレーをシミュレートする良い例を挙げました。要点は、既存の複数パラメータ関数のうち、パラメータ(特定の自治体の価値を超えて販売税を計算するための関数を作成する関数)を作成することができるということです。

ここで魅力的なのは、Cook Countyで消費税を計算するために特別な機能を別途に用意する代わりに、実行時に機能を動的に作成(および再利用)することができます。

+0

+1、あなたのブログのリンクは壊れているように見えますが、私はあなたの例が実際の関数をC#でカリングしていて、シミュレートされていないと思われます。この答えは、一部の機能アプリケーションと混同するのではなく、部分的な機能アプリケーションを可能にするものとして実際にカリングを記述する唯一の答えです。 – phoog

+0

私は最近CommunityServerからSitefinityにブログを移行しました。私は私の古いブログのデータをインポートするユーティリティを書くことに慣れていません:(しかし、私はカリングをシミュレートするために関数のオーバーロードを使いました。 2番目に戻り、結果を返します。本当のカレーと同じくらいエレガントではありませんが動作します;) –

9

curryingは、複数の引数を持つ関数を単一引数の関数の連鎖に変換するプロセスを記述します。 C#での例は、3引数の機能のために:今すぐ

Func<T1, Func<T2, Func<T3, T4>>> Curry<T1, T2, T3, T4>(Func<T1, T2, T3, T4> f) 
{ 
    return a => b => c => f(a, b, c); 
} 

void UseACurriedFunction() 
{ 
    var curryCompare = Curry<string, string, bool, int>(String.Compare); 
    var a = "SomeString"; 
    var b = "SOMESTRING"; 
    Console.WriteLine(String.Compare(a, b, true)); 
    Console.WriteLine(curryCompare(a)(b)(true)); 

    //partial application 
    var compareAWithB = curryCompare(a)(b); 
    Console.WriteLine(compareAWithB(true)); 
    Console.WriteLine(compareAWithB(false)); 
} 

、boolean型の引数はおそらくあなたが最も可能性の高い部分のアプリケーションで開いたままにしておきたいと思いません引数です。これは、F#関数の引数の順序が最初は少し奇妙に見える理由の1つです。さんが別のC#のカレー関数を定義してみましょう:

void UseADifferentlyCurriedFunction() 
{ 
    var curryCompare = BackwardsCurry<string, string, bool, int>(String.Compare); 

    var caseSensitiveCompare = curryCompare(false); 
    var caseInsensitiveCompare = curryCompare(true); 

    var format = Curry<string, string, string, string>(String.Format)("Results of comparing {0} with {1}:"); 

    var strings = new[] {"Hello", "HELLO", "Greetings", "GREETINGS"}; 

    foreach (var s in strings) 
    { 
     var caseSensitiveCompareWithS = caseSensitiveCompare(s); 
     var caseInsensitiveCompareWithS = caseInsensitiveCompare(s); 
     var formatWithS = format(s); 

     foreach (var t in strings) 
     { 
      Console.WriteLine(formatWithS(t)); 
      Console.WriteLine(caseSensitiveCompareWithS(t)); 
      Console.WriteLine(caseInsensitiveCompareWithS(t)); 
     } 
    } 
} 

はなぜC#でこれらの例を示します。

Func<T3, Func<T2, Func<T1, T4>>> BackwardsCurry<T1, T2, T3, T4>(Func<T1, T2, T3, T4> f) 
{ 
    return a => b => c => f(c, b, a); 
} 

を今、私たちは、もう少し便利な何かを行うことができますか? F#では、関数の宣言がデフォルトでカリーされるためです。あなたは通常、機能をカレーする必要はありません。彼らはすでにカレーです。主な例外は、複数の引数を含むタプルを取るフレームワークメソッドやその他のオーバーロードされた関数です。したがって、このような関数をカレー化したいかもしれません。実際に、私がこれを行うライブラリ関数を探していたとき、私はこの問題に遭遇しました。それが実装することは非常に簡単ですので、私は(確かにそれがある場合)、それが欠落しているとします

let curry f a b c = f(a, b, c) 

//overload resolution failure: there are two overloads with three arguments. 
//let curryCompare = curry String.Compare 

//This one might be more useful; it works because there's only one 3-argument overload 
let backCurry f a b c = f(c, b, a) 
let intParse = backCurry Int32.Parse 
let intParseCurrentCultureAnyStyle = intParse CultureInfo.CurrentCulture NumberStyles.Any 
let myInt = intParseCurrentCultureAnyStyle "23" 
let myOtherInt = intParseCurrentCultureAnyStyle "42" 

String.Compareと失敗を回避するには、私の知る限り、3-を指定する方法はありませんので、他の回答は、そのすでにカバーしているので、私はF#で一部機能のアプリケーションの使用についての詳細には触れません

let curryCompare s1 s2 (b:bool) = String.Compare(s1, s2, b) 
let backwardsCurryCompare (b:bool) s1 s2 = String.Compare(s1, s2, b) 

:引数は選択するオーバーロードは、非一般的なソリューションを使用することができます。

+1

これは受け入れられる回答です –