84

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

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

所以我的问题是,两者之间的主要区别是什么,你什么时候会使用一个而不是另一个?这是否只是一个示例过于简单,无法说明为什么要使用一个而不是另一个?

4

5 回答 5

88

在Plasty Grove 链接的答案中已经很好地解释了语义差异。

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

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

所以我们得到了一个带 的部分应用<function1>Int因为我们已经给了它第一个整数。到现在为止还挺好。现在来咖喱:

scala> def modNCurried(n: Int)(x: Int): Boolean = ((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)

因此,多参数列表符号似乎并没有立即创建一个柯里化函数(假设是为了避免不必要的开销),而是等待您明确声明您希望它柯里化(该符号还有其他一些优点):

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

这与我们之前得到的完全一样,所以这里没有区别,除了符号。另一个例子:

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

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

这演示了如何部分应用“普通”函数会导致一个函数接受所有参数,而部分应用具有多个参数列表的函数会创建一个函数链,每个参数列表一个,所有函数都返回一个新函数:

scala> def foo(a:Int, b:Int)(x:Int)(y:Int): 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有两个参数,所以柯里化链中的第一个函数有两个参数。


总之,就功能而言,部分应用的函数与柯里化函数并没有真正的不同。这很容易验证,因为您可以将任何函数转换为咖喱函数:

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

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

后经

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


另外:正如@a​​sflierl 正确指出的那样,Scala 在部分应用“正常”函数时似乎无法推断类型:

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>

这个答案显示了这如何非常有用。

于 2013-01-14T09:42:49.633 回答
19

柯里化与元组有关:将一个接受元组参数的函数转换为接受 n 个单独参数的函数,反之亦然。记住这一点是区分 curry 与部分应用程序的关键,即使在不完全支持 curry 的语言中也是如此。

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.

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

如果您只是认为柯里化是与元组有关的转换,那么很容易记住。

在默认柯里化的语言(例如 Haskell)中,区别很明显——您必须实际做一些事情才能在元组中传递参数。但是大多数其他语言,包括 Scala,默认情况下都是非咖喱的——所有的参数都作为元组传递,所以 curry/uncurry 的用处要小得多,也不那么明显。人们甚至最终认为部分应用和柯里化是一回事——只是因为它们不能轻易地表示柯里化函数!

于 2013-01-14T14:25:15.663 回答
2

多变量函数:

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

柯里化(或柯里化函数):

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

因此,与柯里化相比,它不是部分应用的功能。这是多变量函数。与部分应用函数相媲美的是柯里化函数的调用结果,它是一个与部分应用函数具有相同参数列表的函数。

于 2014-07-14T20:49:00.997 回答
0

只是为了澄清最后一点

另外:正如@a​​sflierl 正确指出的那样,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,_)
              ^
于 2016-08-11T18:00:06.343 回答
0

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

Currying:将具有多个参数的函数分解为一系列单参数函数。请注意,Scala 允许将一个函数作为参数传递给另一个函数。

函数的部分应用:传递给函数的参数少于其声明中的参数。当您为函数提供的参数较少时,Scala 不会抛出异常,它只是应用它们并返回一个新函数,其中包含需要传递的其余参数。

于 2020-03-01T14:02:27.200 回答