我意识到这里有几个关于 什么 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)
。
所以我的问题是,两者之间的主要区别是什么?你何时会使用另一个?这是一个过于简单的例子来说明为什么会使用另一个?
答案 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>
注意:您的示例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)
只是澄清最后一点
另外:正如@asflierl正确指出的那样,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不会引发异常,它仅应用它们并返回一个带有需要传递的其余参数的新函数。