【问题标题】:Understanding Currying in Scala理解 Scala 中的柯里化
【发布时间】:2017-01-13 22:06:00
【问题描述】:

我在理解柯里化概念或至少是 SCALA 柯里化符号方面遇到了问题。

wikipedia 说,柯里化是将接受多个参数的函数的求值转换为对一系列函数进行求值的技术,每个函数都有一个参数。

按照这个解释,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?

所以,如果我没看错的话,addCurr 只是函数 add 的语法糖,两者都是 curryed 函数?

根据前面的例子,next函数也等价于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)}

【问题讨论】:

    标签: scala functional-programming currying


    【解决方案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 中,(=&gt;)函数 与 (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扩展

    如您所料,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 扩展,并且我可以在方法定义站点享受更简洁的语法。

    【讨论】:

    • 那么在你的例子中,areadyFunction 是一个返回函数的方法?
    • @Darth_Tato 它根本不是一个方法,它是一个返回另一个 (=&gt;) 函数的 (=&gt;) 函数。
    【解决方案2】:

    Curried 方法是语法糖,这部分你是对的。但是这个语法糖有点不同。考虑以下示例:

    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")
    

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

    错误:(75, 19) 对象 XXX 中的柯里化方法缺少参数列表 未应用的方法仅在函数类型时转换为函数 是期待。您可以通过编写 curried_ 或 curried(_)(_) 而不是 curried 来明确此转换。

    【讨论】:

    • 你的代码有错误吗?您定义了 functionFirst2,但您没有对它做任何事情。 functionFirst 和 functionSecond 是相同的。你还有别的意思吗?
    • 是的,我知道 addCur 返回一个字符串,所以我不能做val x = addCur("apple"),因为编译期望返回另一个函数来进行部分应用并且这个函数返回一个字符串,但是这个有效val x = addCur("apple")_。使用 add,这是有效的val x = add("apple"),因为它返回一个函数。对吗??
    【解决方案3】:

    你的问题让我很感兴趣,所以我自己尝试了一下。他们实际上对一些非常不同的结构进行了脱糖。使用

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

    这实际上编译为

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

    因此它完全消除了任何柯里化效果,使其成为常规的 arity-2 方法。 Eta扩展是用来让你咖喱它的。

    def add(a:String): String =&gt; String = {b =&gt; a + " " + b}

    这个工作如你所料,编译成一个返回 Function1[String,String] 的方法

    【讨论】:

    • 所以他们不一样?我的意思是,两者都做同样的事情,但是如果它们的编译方式不同,那么在某些方面肯定比另一个更好。你怎么看?
    • 我没有做任何测试,但我怀疑def curr(a: String)(b: String):String 的形式通常会更好(尽管它可能取决于大小写)。直观地说,因为在您提供所有参数并且不对函数进行柯里化的情况下,它只是一个常规方法调用,没有额外的对象创建或间接调用,并且因为语言设计者选择不将其编译为您的其他形式已发布。
    • 谢谢@Puhlen。我更喜欢def curr(a: String)(b: String)..这样的表达方式
    猜你喜欢
    • 2018-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-20
    • 1970-01-01
    • 1970-01-01
    • 2015-08-17
    • 2021-02-07
    相关资源
    最近更新 更多