理解Scala中的Currying

时间:2017-01-13 22:06:00

标签: scala functional-programming currying

我在理解currying概念方面遇到了问题,或者至少是SCALA currying表示法。

wikipedia说currying是一种翻译函数求值的技术,该函数将多个参数计算为一系列函数,每个函数都有一个参数。

根据这个解释,scala的下两行是否相同?

def addCurr(a: String)(b: String): String = {a + " " + b}
def add(a:String): String => String = {b => a + " " + b}

我用相同的字符串运行两行a和b获得相同的结果,但我不知道它们是否在引擎盖下是不同的

我对addCurr(和currying本身)的思考方式是它是一个接收字符串参数a的函数,并返回另一个函数,该函数也接收一个字符串参数b并返回字符串a +“”+ b?< / p>

所以如果我说得对,addCurr只是函数add的语法糖,而且都是curryed函数?

根据前面的例子,scala的下一个函数也是等价的?

def add(a: String)(b: String)(c: String):String = { a + " " + b + " " + c}

def add1(a: String)(b: String): String => String = {c => a + " " + b + " " + c}

def add2(a:String): (String => (String => String)) = {b => (c => a + " " + b + " " + c)}

3 个答案:

答案 0 :(得分:1)

它们有一些不同的语义,但它们的用例大致相同,实际上和它在代码中的外观都是一样的。

柯里

在数学意义上对Scala中的函数进行卷曲是非常简单的:

val function = (x: Int, y: Int, z: Int) => 0
// function: (Int, Int, Int) => Int = <function3>
function.curried
// res0: Int => (Int => (Int => Int)) = <function1>

功能&amp;方法

您似乎对Scala中的事实感到困惑,(=>函数与(def方法不同。方法不是一流的对象,而功能是(即它有curriedtupled方法,Function1有更多优点。

然而,可以通过称为eta扩展的操作将方法提升到函数。有关详细信息,请参阅this SO answer。您可以通过编写methodName _手动触发它,或者如果您给出了一个预期函数类型的方法,它将隐式完成。

def sumAndAdd4(i: Int, j: Int) = i + j + 4
// sumAndAdd4.curried // <- won't compile

val asFunction = sumAndAdd4 _ // trigger eta expansion
// asFunction: (Int, Int) => Int = <function2>
val asFunction2: (Int, Int) => Int = sumAndAdd4
// asFunction2: (Int, Int) => Int = <function2>
val asFunction3 = sumAndAdd4: (Int, Int) => Int
// asFunction3: (Int, Int) => Int = <function2>


asFunction.curried
// res0: Int => (Int => Int) = <function1>
asFunction2.curried
// res1: Int => (Int => Int) = <function1>
asFunction3.curried
// res2: Int => (Int => Int) = <function1>
{sumAndAdd4 _}.tupled // you can do it inline too
// res3: Int => (Int => Int) = <function1>

Eta扩展多个参数列表

就像您预期的那样,eta扩展会将每个参数列表提升到自己的功能

def singleArgumentList(x: Int, y: Int) = x + y
def twoArgumentLists(x: Int)(y: Int) = x + y

singleArgumentList _ // (Int, Int) => Int
twoArgumentLists _ // Int => (Int => Int) - curried!

val testSubject = List(1, 2, 3)

testSubject.reduce(singleArgumentList) // Int (6)
testSubject.map(twoArgumentLists) // List[Int => Int]

// testSubject.map(singleArgumentList) // does not compile, map needs Int => A
// testSubject.reduce(twoArgumentLists) // does not compile, reduce needs (Int, Int) => Int

但这不是数学意义上的讨论:

def hmm(i: Int, j: Int)(s: String, t: String) = s"$i, $j; $s - $t"

{hmm _} // (Int, Int) => (String, String) => String

这里,我们得到两个参数的函数,返回两个参数的另一个函数。

并不是那么直截了当地指出它的一些论点

val function = hmm(5, 6) _ // <- still need that underscore!

与函数一样,你可以毫不费力地找回函数:

val alreadyFunction = (i: Int, j: Int) => (k: Int) => i + j + k

val f = alreadyFunction(4, 5) // Int => Int

你喜欢哪种方式 - Scala对许多事情都不太自以为是。我个人更喜欢多个参数列表,因为我经常需要部分应用一个函数,然后将它传递到某个地方,其余的参数将被给出,所以我不需要明确地做eta -expansion,我可以在方法定义站点享受更简洁的语法。

答案 1 :(得分:0)

咖喱方法是语法糖,你对这一部分是正确的。但这种语法糖有点不同。请考虑以下示例:

def addCur(a: String)(b: String): String = { a + b }

def add(a: String): String => String = { b => a + b }

val functionFirst: String => String = add("34")
val functionFirst2 = add("34")_
val functionSecond: String => String = add("34")

一般来说,curried方法允许部分应用,并且是scala implicits机制工作所必需的。在上面的示例中,我提供了使用示例,如您在第二个示例中所示,我们必须使用下划线符号来允许编译器执行“技巧”。如果它不存在,您将收到类似于以下错误的错误:

  

错误:(75,19)缺少对象XXX中的方法的参数列表   未应用的方法仅在函数类型时转换为函数   是期待。您可以通过撰写curried_curried(_)(_)代替curried来明确转换此内容。

答案 2 :(得分:0)

你的问题让我很感兴趣,所以我尝试了这一点。他们实际上是对一些非常不同的结构。使用

def addCurr(a: String)(b: String): String = {a + " " + b}

这实际上编译为

def addCurr(a: String, b: String): String = {a + " " + b}

因此它完全消除了任何currying效果,使其成为常规的arity-2方法。 Eta扩展用于让您进行咖喱调制。

def add(a:String): String => String = {b => a + " " + b}

这个工作正如您所期望的那样,编译为返回Function1 [String,String]

的方法