27

我最近开始学习 F# 并遇到了一些简单示例的 curried 函数,例如:

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

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

该函数的类型为

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

即取一对floatandint并返回 a float

我们可以把它写成一个柯里化函数

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

该函数的类型为

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

即接受 afloat并返回to的函数intfloat

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

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

两者都给

val it : float = 99.0

但是,使用 curried 函数,我可以输入特定项目的价格以获得新功能。例如

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

然后salesBeer 211.98salesWine 229.98

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

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

并得到

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

这似乎是件好事。因此,当我想用​​带有参数的命令式语言实现一个函数时,我是否n > 1应该总是在 F# 中使用柯里化函数(只要参数是独立的)?n或者我应该采取简单的路线并在必要时使用带有 -tuple 的常规函数​​并稍后使用 curry?或者是其他东西?

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

4

4 回答 4

32

当您在 curried 和 tupled 形式之间进行选择时,要考虑的主要事情是您作为参数的 tuple 是否意味着任何东西。

元组形式。例如,float * float可能表示一个范围,然后使用元组形式是一个好主意。

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

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

当你有一个“更重要”的论点时,柯里化形式也很有用,因为你可以使用流水线。例如,List.map必须进行咖喱才能允许这样做:

[1 .. 10] |> List.map (fun n -> n + 1)
于 2013-09-10T14:31:38.023 回答
3

或者我应该采取简单的路线并使用带有n元组的常规函数​​并在必要时稍后使用curry?

你觉得做更多的工作更简单吗?let stuff xyz = ... 不仅比 let stuff (x, y, z) 输入更少,而且实际上语言所做的工作更少。第二个版本必须分配一个元组,然后将元组解构为参数,而第一个版本只使用参数。

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

于 2013-09-10T13:07:40.243 回答
3

另一个考虑因素 - 如果您计划与 C# 或 VB .NET 进行互操作,请不要使用 curried 形式,因为它们不会很好地暴露给这些语言。另一方面,从 C#/VB .NET 的角度来看,元组形式作为一组普通参数公开,使用起来非常自然。

于 2013-09-17T23:07:49.803 回答
2

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

考虑一下:

foo bar(x,y)

在所有允许这种语法的 ALGOL 风格的语言中,这意味着“bar使用xy作为参数调用,并将结果传递给foo” - whenfoo不必是方法(它可以是语法,如print2.* Python 中)。

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

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

foo bar x y

这和错误一样foo bar(x,y)- 但几乎没有那么令人困惑!即使是不熟悉 Lambda 风格语言的程序员也可以很容易地判断出这bar不是将在 中调用的第一个函数foo bar x y。错误马上就清楚了。

于 2013-09-10T16:16:52.820 回答