2008-08-12 105 views

回答

19

(編輯:小Ocaml FP Koan開始做事了)

柯里的公案(約食物公案,即不是食品)

學生來到對雅克·加里古說:「我不明白什麼咖喱是好的。」雅克回答說,「告訴我你最喜歡的餐點和你最喜歡的甜點」。這位疑惑的學生回答說,他喜歡大阪燒和卡寧,但是當他最喜歡的餐廳供應很棒的大阪燒時,他們的筷子總是在第二天早上給他肚子疼。所以雅克帶着學生在一家餐廳裏吃了大阪燒,然後把他帶到了一家商店,那家餐館做得非常出色,在那裏學生們愉快地運用了剩下的食慾。這個學生是坐着的,但他沒有開悟......直到第二天早上他醒來時,他的肚子感覺很好。

我的例子將介紹如何使用它的重用和代碼封裝。一旦你看到這些,這是相當明顯的,並且應該給你一個具體的,簡單的例子,你可以考慮在許多情況下應用。

我們想要在樹上做一個地圖。如果需要多於一個參數的話,這個函數可以被curry應用到每個節點上 - 因爲我們會在節點上應用這個函數作爲最終的參數。它不需要被搞熟,但是編寫另一個函數(假設這個函數在其他變量的其他實例中被使用)將是浪費。

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 

所以這種簡單的情況下是不能令人信服的。當你更多地使用這種語言並自然地遇到這些情況時,它確實是非常強大的。另一個例子是將一些代碼重用爲currying。 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 

好了,現在羅蘭和cloitre是咖喱的功能,因爲他們有自由變量,我們可以得到任何指標是不知道或擔心f_recurrence序列。

+3

這個答案描述了部分函數應用,[which (http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog 2013-02-09 04:29:17

2

這是一個相當簡單的過程。拿一個函數,綁定它的一個參數並返回一個新的函數。例如:

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

現在,通過討好簡單concatStrings功能,您可以輕鬆地添加一個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

這個答案描述了部分函數應用[它與currying有關,但不是同一件事](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog 2013-02-09 04:30:37

14

雖然前面的例子回答了這個問題,但下面是兩個簡單的例子,說明Currying如何對F#編程有利。

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家族的功能!在咖喱版中,請注意明顯缺乏lambda。

// 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

這個答案描述了部分函數的應用,[這與currying有關,但不是相同的東西]( http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog 2013-02-09 04:30:04

2

你知道你可以將函數映射到列表上嗎?例如,映射函數來添加一個到列表中的每個元素:

> 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

這個答案描述了部分函數的應用,[這與currying有關,但不是相同的東西](http://en.wikipedia.org/wiki/柯里#Contrast_with_partial_function_application) – phoog 2013-02-09 04:30:53

+0

@phoog這個答案正確的解析ains說:「不用捲曲你就不能部分應用這些功能」。 – 2013-02-09 08:58:40

1

我給了一個很好的模擬C#中的柯里化的例子#on my blog。要點是您可以創建一個函數,該函數在一個現有的多參數函數中通過一個參數關閉(在我的示例中,創建一個函數,用於計算關於給定自治市的價值的銷售稅關閉)。

這裏吸引人的地方在於,不必爲庫克縣計算銷售稅專門制定單獨的函數,您可以在運行時動態創建(並重新使用)函數。

+0

+1,儘管您的博客鏈接似乎已被打破,但我懷疑您的示例顯示了C#中的實際功能,並未模擬。這個答案是唯一一個將currying描述爲啓用部分函數應用程序的東西,而不是將其與部分函數應用程序混淆。 – phoog 2013-02-09 04:33:56

9

Currying描述了將具有多個參數的函數轉換爲單參數函數鏈的過程。例如在C#中,對於三參數功能:現在

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#函數中的參數順序起初看起來有點奇怪的原因之一。讓我們定義一個不同的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); 
} 

現在,我們可以做一些更有益的:

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#這些例子?因爲在F#中,函數聲明默認爲curried。你通常不需要咖喱功能;他們已經咖喱了。這個主要的例外是框架方法和其他重載函數,它們包含一個包含多個參數的元組。因此,你可能想要咀嚼這樣的功能,而事實上,當我正在尋找一個能夠做到這一點的庫函數時,我就會遇到這個問題。我想這是缺少(如果確實是),因爲它是很容易實現:

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-參數超載來接,你可以使用一個非一般的解決方案:

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

我不會去到有關部分功能應用在F#的使用細節,因爲其他的答案已經覆蓋了已經。