在Scala中看编程(控件抽象)我看到这两个具有相同效果的例子:
def withPrintWriter(file: File, op: PrintWriter => Unit) {
val writer = new PrintWriter(file)
try {
op(writer)
} finally {
writer.close()
}
}
def withPrintWriter(file: File)(op: PrintWriter => Unit) {
val writer = new PrintWriter(file)
try {
op(writer)
} finally {
writer.close()
}
}
他们之间有什么区别?我们能否始终以两种方式获得相同的结果?
答案 0 :(得分:13)
高阶函数和 curried函数的概念通常以正交方式使用。 高阶函数只是将函数作为参数的函数或返回函数作为结果,它可能或可能不要被咖喱。在一般用法中,引用高阶函数的人通常在谈论一个以另一个函数作为参数的函数。
另一方面, curried函数是返回函数作为其结果的函数。完全curried函数是单参数函数,它返回普通结果或返回完全curried函数。请注意,curried函数必然是高阶函数,因为它返回一个函数作为结果。
因此,您的第二个示例是一个返回高阶函数的curried函数的示例。这是curried函数的另一个例子,它没有将函数作为参数,以各种(nearly equivalent)方式表示:
def plus(a: Int)(b:Int) = a + b
def plus(a: Int) = (b: Int) => a + b
val plus = (a: Int) => (b: Int) => a + b
答案 1 :(得分:5)
高阶函数是将函数作为参数或返回函数或两者兼有的函数。
def f(g: Int => Int) = g(_: Int) + 23
scala> f(_ + 45)
res1: Int => Int = <function1>
scala> res1(4)
res2: Int = 72
这是一个高阶函数,它将函数作为参数并返回另一个函数。如您所见,高阶函数是curry的先决条件。咖喱功能如下所示:
def curry[A,B,C](f: (A,B) => C) = (a: A) => (b: B) => f(a,b)
scala> curry((a: Int, b: Int) => a+b)
res3: Int => (Int => Int) = <function1>
scala> res3(3)
res4: Int => Int = <function1>
scala> res4(3)
res5: Int = 6
所以回答你的问题:它们是两个不同的概念,其中一个(高阶函数)是另一个(currying)的预先获取。
答案 2 :(得分:3)
在语义上,我可以想到一个咖喱和非咖喱功能之间的区别。使用非curried版本时,当您调用withPrintWriter
时,这是一个方法调用。使用curried版本,它实际上将是两个方法调用。可以这样想:
withPrintWriter.apply(file).apply(op)
除此之外,我认为很多人在这种情况下使用currying来表达风格。在这里使用currying使这看起来更像是一个语言功能,然后只是一个自定义函数调用,因为你可以像这样使用它:
withPrintWriter(file){ op =>
...
}
以这种方式使用它是试图从语言本身模拟一些控制结构的痛苦,但同样,这只是一种风格的东西,并且它带来了额外的方法调用的开销。
您可以以几乎相同的方式使用非咖喱版本,但它看起来并不干净:
withPrintWriter(file, { op =>
...
})
修改强> @drexin在他的回答中提出了一个很好的观点,这对我来说值得一提。当你想到该方法的咖喱版本的签名时,它确实是:
Function1[File, Function1[PrintWriter, Unit]]
答案 3 :(得分:1)
它们大多是相同的,但在类型推断方面存在差异。 Scala无法在单个方法调用的参数之间推断类型,但它能够推断出多个参数列表的类型。
考虑:
def foo1[T](x : T, y : T => T) = y(x)
def foo2[T](x : T)(y : T => T) = y(x)
foo1(1, t => t + 1) //does not compile with 'missing parameter type'
foo2(1)(t => t + 1) //compiles
您可以在此答案中看到一些其他信息:Multiple parameter closure argument type not inferred
答案 4 :(得分:0)
严格地说,你给出的例子并不是真正的咖喱,它只有多个参数列表。只是在多种情况下,多个参数列表Scala函数看起来很像curried函数。但是,当您调用多参数列表函数但不填写一个或多个参数列表时,它实际上是部分应用程序的示例,而不是currying。使用其所有参数调用多参数列表只是一个函数调用,而不是每个参数列表一个。
有两个用例,其中多个参数列表函数很有用。第一个是隐式参数的情况,因为所有隐式参数都必须位于与任何显式参数分开的自己的参数列表中。第二个用例是接受其他函数作为参数的函数,因为如果一个函数的参数在它自己的参数列表中,你可以省略括号并只使用大括号,使函数调用看起来像某种控制结构
除此之外,差异纯粹是装饰性的。