Scala currying与部分应用的函数

时间:2013-01-13 23:32:45

标签: scala functional-programming currying

我意识到这里有几个关于 什么 currying和部分应用函数的问题,但我问的是它们是如何不同的。举个简单的例子,这里有一个用于查找偶数的curry函数:

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))

返回:List(2,4,6,8)。但我发现我可以这样做:

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

val p = modN(2, _: Int)
println(filter(nums, p))

也返回:List(2,4,6,8)

所以我的问题是,两者之间的主要区别是什么?你何时会使用另一个?这是一个过于简单的例子来说明为什么会使用另一个?

5 个答案:

答案 0 :(得分:87)

the answer linked to by Plasty Grove中已经很好地解释了语义差异。

但就功能而言,并没有太大的区别。让我们看一些例子来验证这一点。首先,正常功能:

scala> def modN(n: Int, x: Int) = ((x % n) == 0)
scala> modN(5, _ : Int)
res0: Int => Boolean = <function1>

因此,我们得到部分应用的<function1> Int,因为我们已经为它提供了第一个整数。到现在为止还挺好。现在要讨好:

scala> def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

使用这种表示法,您可以天真地期望以下方法起作用:

scala> modNCurried(5)
<console>:9: error: missing arguments for method modN;
follow this method with `_' if you want to treat it as a partially applied function
          modNCurried(5)

所以多参数列表表示法似乎并没有立即创建一个curried函数(假设是为了避免不必要的开销),而是等待你明确声明你想要它的咖喱(符号也有一些other advantages

scala> modNCurried(5) _
res24: Int => Boolean = <function1>

这与我们以前完全相同,所以这里没有区别,除了符号。另一个例子:

scala> modN _
res35: (Int, Int) => Boolean = <function2>

scala> modNCurried _
res36: Int => (Int => Boolean) = <function1>

这表明如何部分应用&#34;正常&#34;函数产生一个获取所有参数的函数,而部分应用具有多个参数列表的函数会创建一个函数链,每个参数列表一个,它们都返回一个新函数:

scala> def foo(a:Int, b:Int)(x:Int)(y:Int) = a * b + x - y
scala> foo _
res42: (Int, Int) => Int => (Int => Int) = <function2>

scala> res42(5)
<console>:10: error: not enough arguments for method apply: (v1: Int, v2: Int)Int => (Int => Int) in trait Function2.
Unspecified value parameter v2.

如您所见,因为foo的第一个参数列表有两个参数,咖喱链中的第一个函数有两个参数。


总之,部分应用的功能在功能方面与curried功能完全不同。鉴于您可以将任何功能转换为咖喱功能,这很容易验证:

scala> (modN _).curried
res45: Int => (Int => Boolean) = <function1

scala> modNCurried _
res46: Int => (Int => Boolean) = <function1>

Post Scriptum

注意:您的示例println(filter(nums, modN(2))modN(2)之后没有下划线的情况下工作的原因似乎是Scala编译器只是假设下划线为程序员提供方便。


添加:正如@asflierl正确指出的那样,Scala似乎无法在部分应用&#34;正常&#34;时推断出类型。功能:

scala> modN(5, _)
<console>:9: error: missing parameter type for expanded function ((x$1) => modN(5, x$1))

尽管该信息可用于使用多参数列表表示法编写的函数:

scala> modNCurried(5) _
res3: Int => Boolean = <function1>

This answers显示了这非常有用。

答案 1 :(得分:19)

currying与元组有关:将一个带有元组参数的函数转换为一个带有n个独立参数的函数,反之亦然。记住这是区分咖喱与部分应用的关键,即使在不能干净地支持咖喱的语言中也是如此。

curry :: ((a, b) -> c) -> a -> b -> c 
   -- curry converts a function that takes all args in a tuple
   -- into one that takes separate arguments

uncurry :: (a -> b -> c) -> (a, b) -> c
   -- uncurry converts a function of separate args into a function on pairs.

部分应用程序是将函数应用于某些参数的能力,为剩余的参数产生新函数

如果您认为currying是与元组相关的转换,那么很容易记住。

在默认情况下使用的语言(例如Haskell)中,区别很明显 - 您必须实际执行某些操作才能在元组中传递参数。但是大多数其他语言,包括Scala,在默认情况下都是不受限制的 - 所有args都是作为元组传递的,所以curry / uncurry远没那么有用,也不太明显。人们甚至认为部分应用和currying是一回事 - 只是因为它们不能轻易代表curried函数!

答案 2 :(得分:2)

多变量函数:

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

Currying(或curried函数):

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

所以它不是部分应用的功能,可以与currying相媲美。这是多变量函数。 与部分应用函数相当的是curried函数的调用结果,该函数具有与部分应用函数具有相同参数列表的函数。

答案 3 :(得分:0)

只是澄清最后一点

  

另外:正如@a​​sflierl正确指出的那样,Scala看起来并不像   部分应用“正常”时能够推断出类型   功能:

如果所有参数都是通配符,Scala可以推断出类型,但是当指定其中一些参数时,Scala可以推断出类型,而其中一些参数不是。

scala> modN(_,_)
res38: (Int, Int) => Boolean = <function2>

scala> modN(1,_)
<console>:13: error: missing parameter type for expanded function ((x$1) => modN(1, x$1))
       modN(1,_)
              ^

答案 4 :(得分:0)

到目前为止我能找到的最好的解释:https://dzone.com/articles/difference-between-currying-amp-partially-applied

固化:将具有多个参数的函数分解为单参数函数链。 注意,Scala允许将一个函数作为参数传递给另一个函数。

函数的部分应用:向函数传递的参数少于其声明中的参数。当您为函数提供较少的参数时,Scala不会引发异常,它仅应用它们并返回一个带有需要传递的其余参数的新函数。