Scala currying与部分应用的函数

Eri*_*ric 81 functional-programming scala 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)
Run Code Online (Sandbox Code Playgroud)

所以你可以编写以下内容来使用它:

val nums = List(1,2,3,4,5,6,7,8)
println(filter(nums, modN(2))
Run Code Online (Sandbox Code Playgroud)

返回:List(2,4,6,8).但我发现我可以用这种方式做同样的事情:

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

val p = modN(2, _: Int)
println(filter(nums, p))
Run Code Online (Sandbox Code Playgroud)

还返回:List(2,4,6,8).

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

fre*_*oma 87

与Plasty Grove相关的答案中,语义差异得到了很好的解释.

但就功能而言,似乎并没有多大区别.让我们看一些例子来验证这一点.一,正常功能:

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

因此,我们得到了部分应用<function1>,它接受一个Int,因为我们已经给它的第一个整数.到现在为止还挺好.现在来讨论:

scala> def modNCurried(n: Int)(x: Int) = ((x % n) == 0)
Run Code Online (Sandbox Code Playgroud)

使用这种表示法,你会天真地期望以下工作:

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)
Run Code Online (Sandbox Code Playgroud)

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

scala> modNCurried(5) _
res24: Int => Boolean = <function1>
Run Code Online (Sandbox Code Playgroud)

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

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

scala> modNCurried _
res36: Int => (Int => Boolean) = <function1>
Run Code Online (Sandbox Code Playgroud)

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

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.
Run Code Online (Sandbox Code Playgroud)

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


总之,在功能方面,部分应用的功能与curried功能并没有真正不同.鉴于您可以将任何函数转换为curried函数,这很容易验证:

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

scala> modNCurried _
res46: Int => (Int => Boolean) = <function1>
Run Code Online (Sandbox Code Playgroud)

Post Scriptum

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


另外:正如@asflierl正确指出的那样,Scala似乎无法在部分应用"普通"函数时推断出类型:

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

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

scala> modNCurried(5) _
res3: Int => Boolean = <function1>
Run Code Online (Sandbox Code Playgroud)

这个答案显示了这是如何非常有用的.


Don*_*art 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.
Run Code Online (Sandbox Code Playgroud)

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

如果你认为currying是与元组有关的转换,那么很容易记住.

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