我在理解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)}
答案 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>
您似乎对Scala中的事实感到困惑,(=>
)函数与(def
)方法不同。方法不是一流的对象,而功能是(即它有curried
和tupled
方法,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扩展会将每个参数列表提升到自己的功能
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]
的方法