匿名的 Scala 函数语法

15

我正在学习Scala,但是在阅读http://www.scala-lang.org/node/135中的匿名函数示例时遇到了一些困难。我复制了整个代码块如下:

object CurryTest extends Application {
    def filter(xs: List[Int], p: Int => Boolean): List[Int] =
        if (xs.isEmpty) xs
        else if (p(xs.head)) xs.head :: filter(xs.tail, p)
        else filter(xs.tail, p)

    def modN(n: Int)(x: Int) = ((x % n) == 0)

    val nums = List(1, 2, 3, 4, 5, 6, 7, 8)
    println(filter(nums, modN(2)))
    println(filter(nums, modN(3)))
}

我对modN函数的应用感到困惑

def modN(n: Int)(x: Int) = ((x % n) == 0)
在这个例子中,它只使用一个参数调用。
modN(2) and modN(3)

modN(n: Int)(x: Int)的语法是什么意思?

由于只传递了一个参数,我认为它们不是两个参数,但我无法弄清楚nums中的值如何被mod函数使用。

3个回答

47
这是函数式编程中一个有趣的事物,叫做柯里化。基本上,摩西·舒恩菲克尔和后来的哈斯凯尔·柯里(虽然舒恩菲克尔化听起来很奇怪...)提出了这样的想法:调用多个参数的函数,比如说f(x,y),与调用链{g(x)}(y)g(x)(y)相同,其中g是一个产生另一个函数作为输出的函数。
例如,考虑函数f(x: Int, y: Int) = x + y。对f(2,3)的调用将产生5,正如预期的那样。但是当我们将这个函数柯里化——重新定义为f(x:Int)(y: Int)并将其作为f(2)(3)调用时会发生什么呢?第一次调用f(2)会产生一个取整数y并将2加上它的函数->因此f(2)具有类型Int => Int,等价于函数g(y) = 2 + y。第二次调用f(2)(3)调用新产生的函数g,并用参数3调用它,因此评估为5,正如预期的那样。
另一种观察它的方式是通过逐步进行约简(函数式程序员称之为beta-reduction - 这就像逐行切换的函数式方法)来查看f(2)(3)调用(请注意,以下内容不是真正有效的Scala语法)。
f(2)(3)         // Same as x => {y => x + y}
 | 
{y => 2 + y}(3) // The x in f gets replaced by 2
       |
     2 + 3      // The y gets replaced by 3
       |
       5

所以,经过这么多的讨论,f(x)(y)可以被看作是以下Lambda表达式:(x: Int) => {(y: Int) => x + y} - 这是有效的Scala。

我希望这一切都说得通 - 我试图给出一些关于为什么modN(3)调用有意义的背景 :)


非常好的解释。我把你的答案改成了被接受的答案(而且你还修正了之前示例中的错误)。 - Jeff Storey
点赞“Schonfinkeling”将参数传递到函数中。从现在开始,我只会使用这个词。 :-D - Leonel

3
您正在部分应用ModN函数。部分函数应用是函数式语言的主要特征之一。有关更多信息,请查看柯里化无点风格的文章。

2
在这个例子中,modN 返回 一个特定 N 的取模函数。这样可以避免您执行以下操作:
def mod2(x:Int): Boolean = (x%2) == 0
def mod3(x:Int): Boolean = (x%3) == 0

两对括号限定了你可以停止向方法传递参数的位置。当然,即使方法只有一个参数列表,你也可以使用占位符来实现相同的效果。
def modN(n: Int, x: Int): Boolean = (x % n) == 0

val nums = List(1, 2, 3, 4, 5)
println(nums.filter(modN(2, _)))
println(nums.filter(modN(3, _)))

感谢您详细的回复。在这个带有占位符的第二个例子中,您能解释一下最后的 : Int 是什么意思吗?例如: def modN(n: Int)(x: Int): Int与 def modN(n: Int)(x: Int)这两者之间是否只是语法上的差异,即何时可以使用占位符和何时不能? - Jeff Storey
另外,我刚刚尝试了使用占位符_的第二个示例,编译器报错modN参数数量不正确。 - Jeff Storey
这是因为 modN(x, y) 不是调用函数的有效方式(Scala 不会自动反柯里化 - 即将 f(x)(y) 转换为 f(x, y))。因此,在示例中调用 modN 的正确方法是 modN(2)(_)。另外,小细节 - modN 的返回类型不正确,应该是 modN(n: Int)(x: Int): Boolean = (x % n) == 0。或者你可以让类型推断器推断它。 :) - Flaviu Cipcigan
我更新了第二个示例,使其更加正确。感谢您的解释,Flaviu。 - David Winslow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接