【问题标题】:Scala currying vs partially applied functionsScala 柯里化与部分应用函数
【发布时间】:2012-12-27 21:14:23
【问题描述】:

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

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)

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

【问题讨论】:

  • 部分应用时,在内存中表示 curried 和非 curried 版本的成本可能不同,因此运行时性能也可能会受到影响。 (也就是说,如果优化器不够聪明,无法在两种情况下选择最佳表示。)不过,我对 Scala 还不够熟悉,无法说出确切的区别。
  • 我发现这个解释非常有用,他在一篇文章中解释了部分函数、部分应用函数和柯里化:stackoverflow.com/a/8650639/1287554
  • 优秀的链接,@PlastyGrove。谢谢!
  • 感谢@Utaal 提供的链接。 Martin Odersky 本人的任何回答都非常有价值。我认为这些概念现在开始流行了。

标签: scala functional-programming currying


【解决方案1】:

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

不过,就功能而言,似乎没有太大区别。让我们看一些例子来验证这一点。一、普通功能:

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

所以我们得到一个部分应用的&lt;function1&gt;,它接受一个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)

所以 multiple parameter list 符号似乎并没有真正立即创建一个柯里化函数(假设是为了避免不必要的开销),而是等待您明确声明您希望它柯里化(符号还有一些other advantages):

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>

This answers 展示了这非常有用。

【讨论】:

  • 一个重要的区别是类型推断的工作方式不同:gist.github.com/4529020
  • 谢谢,我添加了关于您的评论的注释:)
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    只是为了澄清最后一点

    补充:正如@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,_)
                  ^
    

    【讨论】:

      【解决方案4】:

      多变量函数:

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

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

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

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

      【讨论】:

        【解决方案5】:

        柯里化与元组有关:将接受元组参数的函数转换为接受 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 的用处要小得多,也不那么明显。人们甚至最终认为部分应用和柯里化是一回事——只是因为它们不能轻易地表示柯里化函数!

        【讨论】:

        • 我完全同意。在 Scala 术语中,该词的原始含义中的“currying”是将具有一个参数列表的函数“转换”为具有多个参数列表的函数的过程。在 Scala 中,这种转换可以使用“.curried”来执行。不幸的是,Scala 似乎有点重载了这个词的含义,因为最初它宁愿被称为“.curry”而不是“.curried”。
        猜你喜欢
        • 2012-03-14
        • 1970-01-01
        • 2021-02-07
        • 2017-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-11
        • 1970-01-01
        相关资源
        最近更新 更多