简体   繁体   English

Scala 柯里化 vs 部分应用函数

[英]Scala currying vs partially applied functions

I realize that there are several questions on here about what currying and partially applied functions are, but I'm asking about how they are different.我意识到这里有几个关于什么是柯里化和部分应用函数的问题,但我问的是它们有什么不同。 As a simple example, here is a curried function for finding even numbers:作为一个简单的例子,这是一个用于查找偶数的柯里化函数:

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)

So you could write the following to use this:因此,您可以编写以下内容来使用它:

val nums = List(1,2,3,4,5,6,7,8)
println(filter(nums, modN(2))

which returns: List(2,4,6,8) .返回: List(2,4,6,8) But I've found that I can do the same thing this way:但我发现我可以用这种方式做同样的事情:

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

val p = modN(2, _: Int)
println(filter(nums, p))

which also returns: List(2,4,6,8) .它还返回: List(2,4,6,8)

So my question is, what's the main difference between the two, and when would you use one over the other?所以我的问题是,两者之间的主要区别是什么,你什么时候会使用一个而不是另一个? Is this just too simplistic of an example to show why one would be used over the other?这是一个太简单的例子来说明为什么一个会被用于另一个?

The semantic difference has been explained fairly well in the answer linked to by Plasty Grove . Plasty Grove 链接的答案中已经很好地解释了语义差异。

In terms of functionality, there doesn't seem much of a difference, though.不过,在功能方面,似乎没有太大区别。 Let's look at some examples to verify that.让我们看一些例子来验证这一点。 First, a normal function:首先是一个正常的函数:

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

So we get a partially applied <function1> that takes an Int , because we've already given it the first integer.所以我们得到了一个部分应用的<function1> ,它接受一个Int ,因为我们已经给了它第一个整数。 So far so good.到现在为止还挺好。 Now to currying:现在开始咖喱:

scala> def modNCurried(n: Int)(x: Int): Boolean = ((x % n) == 0)

With this notation, you'd naively expect the following to work:使用这种表示法,您会天真地期望以下内容起作用:

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)

So the multiple parameter list notation doesn't really seem to create a curried function right away (assumingly to avoid unnecessary overhead) but waits for you to explicitly state that you want it curried (the notation has some other advantages as well):因此,多参数列表符号似乎并没有真正立即创建一个柯里化函数(假设是为了避免不必要的开销),而是等待您明确声明您希望它柯里化(该符号还有一些其他优点):

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

Which is exactly the same thing we got before, so no difference here, except for notation.这与我们之前得到的完全相同,所以这里没有区别,除了符号。 Another example:另一个例子:

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

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

This demonstrates how partially applying a "normal" function results in a function that takes all parameters, whereas partially applying a function with multiple parameter lists creates a chain of functions, one per parameter list which, all return a new function:这演示了部分应用“正常”函数如何导致函数接受所有参数,而部分应用具有多个参数列表的函数会创建一个函数链,每个参数列表一个,所有函数都返回一个新函数:

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.

As you can see, because the first parameter list of foo has two parameters, the first function in the curried chain has two parameters.可以看到,因为foo的第一个参数列表有两个参数,所以柯里化链中的第一个函数有两个参数。


In summary, partially applied functions aren't really different form curried functions in terms of functionality.总之,就功能而言,部分应用函数与柯里化函数并没有真正不同。 This is easily verified given that you can convert any function to a curried one:鉴于您可以将任何函数转换为柯里化函数,这很容易验证:

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

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

Post Scriptum后脚本

Note: The reason that your example println(filter(nums, modN(2)) works without the underscore after modN(2) seems to be that the Scala compiler simply assumes that underscore as a convenience for the programmer.注意:您的示例println(filter(nums, modN(2))modN(2)之后没有下划线的原因似乎是 Scala 编译器简单地假设下划线是为了程序员的方便。


Addition: As @asflierl has correctly pointed out, Scala doesn't seem to be able to infer the type when partially applying "normal" functions:另外:正如@asflierl 正确指出的那样,Scala 在部分应用“正常”函数时似乎无法推断类型:

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

Whereas that information is available for functions written using multiple parameter list notation:而该信息可用于使用多参数列表表示法编写的函数:

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

This answers shows how this can be very useful.这个答案显示了这如何非常有用。

Currying is to do with tuples: turning a function that takes a tuple argument into one that takes n separate arguments, and vice versa .柯里化与元组有关:将一个接受元组参数的函数变成一个接受 n 个单独参数的函数,反之亦然 Remembering this is the key to distinguishing curry vs partial application, even in languages that don't cleanly support currying.记住这是区分柯里应用和部分应用的关键,即使在不完全支持柯里化的语言中也是如此。

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.

Partial application is the ability to apply a function to some arguments, yielding a new function for the remaining arguments .部分应用是将函数应用于某些参数的能力,为剩余的参数生成一个新函数

It is easy to remember if you just think currying is the transformation to do with tuples.如果您只是认为柯里化是对元组的转换,那么很容易记住。

In languages that are curried by default (such as Haskell) the difference is clear -- you have to actually do something to pass arguments in a tuple.在默认情况下柯里化的语言(例如 Haskell)中,区别很明显——您必须实际做一些事情来在元组中传递参数。 But most other languages, including Scala, are uncurried by default -- all args are passed as tuples, so curry/uncurry is far less useful, and less obvious.但是大多数其他语言,包括 Scala,在默认情况下都是非柯里化的——所有参数都作为元组传递,所以 curry/uncurry 的用处要小得多,也不那么明显。 And people even end up thinking that partial application and currying are the same thing -- just because they can't represent curried functions easily!人们甚至最终认为部分应用和柯里化是一回事——仅仅因为它们不能轻易地表示柯里化函数!

Multivariable function:多变量函数:

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

Currying (or the curried function):柯里化(或柯里化函数):

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

So it's not partially applied function that's comparable to currying.所以它不是可与柯里化相媲美的部分应用函数。 It's the multivariable function.是多变量函数。 What's comparable to partially applied function is the invocation result of a curried function, which is a function with the same parameter list that the partially applied function has.可与部分应用函数相媲美的是柯里化函数的调用结果,该函数与部分应用函数具有相同的参数列表。

Just to clarify on the last point只是为了澄清最后一点

Addition: As @asflierl has correctly pointed out, Scala doesn't seem to be able to infer the type when partially applying "normal" functions:另外:正如@a​​sflierl 正确指出的那样,Scala 在部分应用“正常”函数时似乎无法推断类型:

Scala can infer types if all parameters are wildcards but not when some of them are specified and some of them not.如果所有参数都是通配符,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,_)
              ^

The best explanation I could find so far: https://dzone.com/articles/difference-between-currying-amp-partially-applied到目前为止我能找到的最好的解释: https : //dzone.com/articles/difference-between-currying-amp-partially-applied

Currying: decomposition of functions with multiple arguments into a chain of single-argument functions.柯里化:将具有多个参数的函数分解为单参数函数链。 Notice, that Scala allows passing a function as an argument to another function.请注意,Scala 允许将函数作为参数传递给另一个函数。

Partial application of function: pass to a function fewer arguments than it has in its declaration.函数的部分应用:传递给函数的参数少于其声明中的参数。 Scala does not throw an exception when you provide fewer arguments to the function, it simply applies them and returns a new function with rest of arguments that need to be passed.当您向函数提供较少的参数时,Scala 不会抛出异常,它只是应用它们并返回一个带有需要传递的其余参数的新函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM