任何人都有一个不错的例子,最好是实用/有用的,他们可以发布演示这个概念吗?
6 回答
(编辑:一个小的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
所以这个简单的案例没有说服力。但是,一旦您更多地使用该语言并自然而然地遇到这些情况,它确实很强大。另一个将代码重用为柯里化的示例。创建素数的递归关系。那里有很多相似之处:
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
好的,现在 rowland 和 cloitre 是柯里化函数,因为它们有自由变量,我们可以在不知道或担心 f_recurrence 的情况下获得它的序列的任何索引。
虽然前面的示例回答了这个问题,但这里有两个更简单的示例,说明 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 系列函数进行 curry!在 curried 版本中,请注意明显缺少 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];;
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));
}
现在,布尔参数可能不是您最可能希望在部分应用程序中保持打开状态的参数。这就是为什么 F# 函数中的参数顺序起初看起来有点奇怪的原因之一。让我们定义一个不同的 C# curry 函数:
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# 中,函数声明默认是柯里化的。您通常不需要 curry 函数;他们已经咖喱了。主要的例外是框架方法和其他重载函数,它们采用包含多个参数的元组。因此,您可能想要对此类函数进行 curry,事实上,当我在寻找可以执行此操作的库函数时,我遇到了这个问题。我想它丢失了(如果确实如此),因为实现起来非常简单:
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# 中部分函数应用程序的使用,因为其他答案已经涵盖了这一点。
这是一个相当简单的过程。获取一个函数,绑定它的一个参数并返回一个新函数。例如:
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(),不如你只拥有一个无法从你下面变异出来的函数。
你知道你可以在一个列表上映射一个函数吗?例如,映射一个函数以向列表的每个元素添加一个:
> 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]]
如果没有 currying,您将无法部分应用这些函数,而必须编写如下内容:
> List.map((fun xs -> List.map((fun n -> n + 1), xs)), [[1; 2]; [3]]);;
val it : int list = [[2; 3]; [4]]
我在我的博客上给出了一个在 C# 中模拟柯里化的好例子。要点是,您可以从现有的多参数函数中创建一个在参数上封闭的函数(在我的示例中,创建一个用于计算在给定城市的价值上封闭的销售税的函数)。
这里吸引人的是,您不必专门为计算库克县的销售税创建一个单独的函数,您可以在运行时动态创建(和重用)该函数。