Scala currying与部分应用函数


82

我意识到这里有几个关于什么是currying和部分应用的函数的问题,但是我在问它们有什么不同。作为一个简单的示例,这是一个用于查找偶数的咖喱函数:

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)

所以我的问题是,两者之间的主要区别是什么?何时使用另一种?这是否太简单了以至于无法说明为什么一个例子要比另一个例子使用?


如果部分应用,则在内存中表示已更新和未更新版本的成本可能会有所不同,因此运行时性能也可能会受到影响。(也就是说,如果优化器不够聪明,无法在两种情况下都选择最佳表示形式。)不过,我对Scala不够熟悉,无法说出确切的区别是什么。


我发现这个解释非常有用,他在一个帖子中解释了部分函数,​​部分应用函数和currying:stackoverflow.com/a/8650639/1287554
Plasty Grove 2013年

优秀的链接,@ PlastyGrove。谢谢!
Eric

感谢@Utaal的链接。马丁·奥德斯基本人的任何回答都很有价值。我认为这些概念现在开始被点击。
Eric

Answers:


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编译器只是假定下划线为程序员提供了便利。


另外:正如@asflierl正确指出的那样,当部分应用“正常”函数时,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>

这个答案说明了它如何非常有用。


一个重要的区别是类型推断的工作原理不同:gist.github.com/4529020

谢谢,我添加了关于您的评论的说明:)
fresskoma 2013年

19

咖喱与元组有关:将一个采用元组参数的函数转换为采用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都作为元组传递,因此咖喱/无咖喱的用处远不那么明显。人们甚至最终认为部分应用程序和currying是同一件事-只是因为它们不能轻松表示咖喱函数!


我完全同意。在Scala术语中,“原始”一词的含义是将具有一个参数列表的功能转换为具有多个参数列表的功能的“过程”。在Scala中,可以使用“ .curried”执行此转换。不幸的是,Scala似乎有点重载了单词的含义,因为最初它更喜欢被称为“ .curry”而不是“ .curried”。
法提赫·科什昆(FatihCoşkun),2015年

2

多变量函数:

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

咖喱(或咖喱函数):

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

因此,不是部分可应用的功能可与currying相提并论。这是多变量函数。与部分应用的函数可比的是curried函数的调用结果,该函数的功能与部分应用的函数具有相同的参数列表。


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,_)
              ^

0

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.