Scala 中的两种柯里化方式;每个的用例是啥?

Posted

技术标签:

【中文标题】Scala 中的两种柯里化方式;每个的用例是啥?【英文标题】:Two ways of currying in Scala; what's the use-case for each?Scala 中的两种柯里化方式;每个的用例是什么? 【发布时间】:2011-06-22 08:24:06 【问题描述】:

我正在我维护的 Scala 样式指南中围绕 Multiple Parameter Lists 进行讨论。我开始意识到currying 有两种方式,我想知道用例是什么:

def add(a:Int)(b:Int) = a + b
// Works
add(5)(6)
// Doesn't compile
val f = add(5)
// Works
val f = add(5)_
f(10) // yields 15

def add2(a:Int) =  b:Int => a + b 
// Works
add2(5)(6)
// Also works
val f = add2(5)
f(10) // Yields 15
// Doesn't compile
val f = add2(5)_

样式指南错误地暗示它们是相同的,而它们显然不是。该指南试图说明创建的柯里化函数,虽然第二种形式不是“按书本”柯里化,但它仍然与第一种形式非常相似(尽管可以说更容易使用,因为你不需要_)

在使用这些表格的人中,对于何时使用一种表格而不是另一种表格的共识是什么?

【问题讨论】:

【参考方案1】:

多参数列表方法

用于类型推断

具有多个参数部分的方法可用于辅助本地类型推断,方法是使用第一部分中的参数来推断类型参数,该参数将为后续部分中的参数提供预期类型。标准库中的foldLeft 就是这方面的典型示例。

def foldLeft[B](z: B)(op: (B, A) => B): B

List("").foldLeft(0)(_ + _.length)

如果这样写的话:

def foldLeft[B](z: B, op: (B, A) => B): B

必须提供更明确的类型:

List("").foldLeft(0, (b: Int, a: String) => a + b.length)
List("").foldLeft[Int](0, _ + _.length)

对于流畅的 API

多参数部分方法的另一个用途是创建一个看起来像语言结构的 API。调用者可以使用大括号代替圆括号。

def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)

loop(2) 
   println("hello!")

将 N 个参数列表应用于具有 M 个参数部分的方法,其中 N _ 的函数,或隐式转换为具有预期类型 FunctionN[..] 的函数。这是一项安全功能,有关背景信息,请参阅 Scala 参考中的 Scala 2.0 更改说明。

柯里化函数

柯里化函数(或简单地说,返回函数的函数)更容易应用于 N 个参数列表。

val f = (a: Int) => (b: Int) => (c: Int) => a + b + c
val g = f(1)(2)

这种小小的便利有时是值得的。请注意,函数不能是参数类型,因此在某些情况下需要方法。

您的第二个示例是混合:返回函数的单参数部分方法。

多阶段计算

柯里化函数还有什么用处?这是一个经常出现的模式:

def v(t: Double, k: Double): Double = 
   // expensive computation based only on t
   val ft = f(t)

   g(ft, k)


v(1, 1); v(1, 2);

我们如何分享结果f(t)?一个常见的解决方案是提供v的矢量化版本:

def v(t: Double, ks: Seq[Double]: Seq[Double] = 
   val ft = f(t)
   ks map k => g(ft, k)

丑!我们纠缠了不相关的问题——计算g(f(t), k)并映射到ks的序列。

val v =  (t: Double) =>
   val ft = f(t)
   (k: Double) => g(ft, k)       

val t = 1
val ks = Seq(1, 2)
val vs = ks map (v(t))

我们也可以使用返回函数的方法。在这种情况下,它更具可读性:

def v(t:Double): Double => Double = 
   val ft = f(t)
   (k: Double) => g(ft, k)       

但是如果我们尝试对具有多个参数部分的方法做同样的事情,我们就会陷入困境:

def v(t: Double)(k: Double): Double = 
                ^
                `-- Can't insert computation here!

【讨论】:

很好的答案;希望我有更多的赞成票而不仅仅是一个。我将消化并应用到样式指南;如果我成功了,这就是选择的答案…… 您可能希望将循环示例更正为:def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body) 这不能编译:val f: (a: Int) => (b: Int) => (c: Int) = a + b + c "将 N 个参数列表应用于具有 M 个参数部分的方法,其中 N 不应该是FunctionX[..],其中X = M-N ? "这不能编译:val f: (a: Int) => (b: Int) => (c: Int) = a + b + c" 我不认为" f: (a: Int) => (b: Int) => (c: Int)" 是正确的语法。可能反义词的意思是“f:Int => Int => Int => Int”。因为 => 是右结合的,所以这实际上是“f: Int => (Int => (Int => Int))”。所以 f(1)(2) 的类型是 Int => Int(即 f 的类型中最里面的位)【参考方案2】:

您只能对函数进行 curry,而不能对方法进行 curry。 add 是一种方法,因此您需要 _ 强制将其转换为函数。 add2 返回一个函数,因此 _ 不仅没有必要,而且在这里毫无意义。

考虑到方法和函数有多么不同(例如,从 JVM 的角度来看),Scala 在模糊它们之间的界限并在大多数情况下做“正确的事情”方面做得很好,但是 strong> 不同,有时您只需要了解它。

【讨论】:

这是有道理的,那么你怎么称呼这种形式的 def add(a:Int)(b:Int) 呢?描述 def 和 def add(a:Int, b:Int) 之间区别的术语/短语是什么? @davetron5000 第一个是具有多个参数列表的方法,第二个是具有一个参数列表的方法。【参考方案3】:

我认为,如果我添加 def add(a: Int)(b: Int): Int,您几乎只需使用 两个 参数定义一个方法,这有助于掌握差异,只有这两个参数被分组到两个参数列表中(参见在其他 cmets 中的后果)。事实上,就 Java(不是 Scala!)而言,该方法只是 int add(int a, int a)。当您编写add(5)_ 时,它只是一个函数字面量,是 b: Int => add(1)(b) 的缩写形式。另一方面,使用add2(a: Int) = b: Int => a + b ,您定义了一个只有一个参数的方法,而对于Java,它将是scala.Function add2(int a)。当您在 Scala 中编写 add2(1) 时,它只是一个普通的方法调用(而不是函数字面量)。

另请注意,如果您立即提供所有参数,add 的开销(可能)比add2 的开销要少。就像add(5)(6) 只是在JVM 级别上转换为add(5, 6),没有Function 对象被创建。另一方面,add2(5)(6) 将首先创建一个包含5Function 对象,然后在其上调用apply(6)

【讨论】:

以上是关于Scala 中的两种柯里化方式;每个的用例是啥?的主要内容,如果未能解决你的问题,请参考以下文章

NuxtJs asyncData 的用例是啥?

Python中“通过”的用例是啥? [复制]

Styled-Components 的 .attrs() 函数的用例是啥?

cudaGraphicsGLRegisterImage中cudaGraphicsRegisterFlagsWriteDiscard的用例是啥?

bash中noop [:]的用例是啥?

DataSet Spark 的用例是啥?