什么时候应该以咖喱形式写我的功能?

时间:2013-09-10 11:50:59

标签: f# functional-programming

我最近开始学习F#并遇到以下简单示例的curried函数:

考虑一个通过将价格p乘以销售单位数n来计算销售额的函数。

let sales (p,n) = p * (float n);;

此功能的类型为

val sales : p:float * n:int -> float

即。取一对floatint并返回float

我们可以把它写成一个curried函数

let salesHi p n = p * (float n);;

此功能的类型为

val salesHi : p:float -> n:int -> float

即。获取float并将int函数返回给float

在简单的情况下,这似乎没有区别

sales (0.99, 100);;
salesHi 0.99 100;;

两者都给予

val it : float = 99.0

然而,通过咖喱功能,我可以提供特定物品的价格以获得新功能。 E.g。

let salesBeer =  salesHi 5.99;;
let salesWine =  salesHi 14.99;;

然后salesBeer 2提供11.98salesWine 2提供29.98

另外,我注意到+之类的内置运算符被定义为函数,所以我可以编写,例如:

let plus2 = (+) 2;
List.map plus2 [1;3;-1];;

并获取

val it : int list = [3; 5; 1]

这似乎是好事。因此,当我想在一个必须采用n > 1参数的命令式语言中实现一个函数时,我是否应该总是在F#中使用一个curried函数(只要参数是独立的)?或者我应该采用简单的路线并使用常规功能n - 元组和咖喱稍后如果有必要?或其他什么?

F#程序员如何决定何时以curry形式创建函数或使用带元组的常规函数​​?

4 个答案:

答案 0 :(得分:31)

当你在curried和tupled形式之间做出选择时,要考虑的主要问题是你作为参数的元组是否意味着

Tupled form。例如,float * float可能代表一个范围,然后使用tupled表单是个好主意。

let normalizeRange (lo, hi) = if hi < lo then (hi, lo) else (lo, hi)
let expandRange by (lo, hi) = (lo - by, hi + by)

关于这一点的好处是你可以编写适用于范围的函数。例如,您可以编写类似的内容:

randomRange() |> normalizeRange |> expandRange 10

Curried form。另一方面,如果所有参数的元组不是具有某些有用含义的独立值,则curried形式是更好的选择。例如,幂函数pown 2.0 10 - 两个参数是幂和指数,但你不可能在你的程序中的其他地方使用元组(2.0, 10)

当你有一个“更重要”的参数时,curried表格也很有用,因为那时你可以使用流水线技术。例如,必须使用List.map来实现此目的:

[1 .. 10] |> List.map (fun n -> n + 1)

答案 1 :(得分:3)

  

或者我应该采用简单的路线,并在必要时使用带有n元组和咖喱的常规功能?

你觉得做更多工作更简单了吗?让东西x y z = ...不仅仅是输入比让东西(x,y,z)更少,它实际上更少的语言工作。第二个版本必须分配一个元组,然后将元组解构为参数,而第一个版本只消耗参数。

curry表单是在F#中编写函数的惯用方法。除非数据已经存储为元组,否则我真的想不到使用元组形式的充分理由。

答案 2 :(得分:3)

另一个考虑因素 - 如果您计划与C#或VB .NET进行互操作,请不要理会咖喱表单,因为它们不能很好地暴露于这些语言。另一方面,从C#/ VB .NET的角度来看,tupled表单作为一组正常的参数公开,并且非常自然地使用。

答案 3 :(得分:2)

元组形式实际上有点危险。它可能看起来类似于ALGOL风格的语言(= 90%的流行语言) - 但它的工作方式不同。

考虑一下:

foo bar(x,y)

在所有允许使用此语法的ALGOL样式语言中,这意味着“以barx作为参数调用y,并将结果传递给foo” - 当foo不必是一个方法时(它可以是语法,如2. * Python中的print。)

但是,在Lambda风格的语言中(如ML,Haskell和F#),这意味着“以foo作为参数调用bar,然后调用结果(这是一个函数)元组(x,y)作为参数。

如果您不熟悉Lambda风格的语言,这可能会令人困惑。现在,如果您使用的是咖喱形式,则相当于:

foo bar x y

这与foo bar(x,y)一样错误 - 但不会令人困惑!即使不熟悉Lambda风格语言的程序员也很容易发现bar不是foo bar x y中要调用的第一个函数。错误很明显。