【问题标题】:Is scala disregading type in function signatures?scala 是否忽略函数签名中的类型?
【发布时间】:2014-12-03 22:00:29
【问题描述】:

我正在学习优秀的 Martin Odersky 的 FP 课程的讲座,其中一堂讲座通过牛顿寻找某些函数的不动点的方法演示了高阶函数。讲座中有一个关键步骤,我认为类型签名被违反了,所以我会要求解释。 (对于入站的冗长介绍表示歉意 - 它觉得它是必要的。)

这样一种算法的实现方式如下:

  val tolerance = 0.0001

  def isCloseEnough(x: Double, y: Double) = abs((x - y) / x) / x < tolerance

  def fixedPoint(f: Double => Double)(firstGuess: Double) = {
    def iterate(guess: Double): Double = {
      val next = f(guess)
      if (isCloseEnough(guess, next)) next
      else iterate(next)
    }
    iterate(firstGuess)
  }

接下来,我们尝试通过 fixedPoint 函数计算平方根,但天真的尝试通过

def sqrt(x: Double) = fixedPoint(y => x / y)(1)

被挫败,因为这种方法会摇摆不定(因此,对于sqrt(2),结果将在 1.0 和 2.0 之间无限交替)。

为了解决这个问题,我们引入了平均阻尼,因此基本上我们计算两个最接近的计算值的平均值并收敛到解,因此

def sqrt(x: Double) = fixedPoint(y => (y + x / y) / 2)(1)

最后,我们介绍averageDamp函数,任务是写sqrtfixedPointaverageDampaverageDamp 定义如下:

def averageDamp(f: Double => Double)(x: Double) = (x + f(x)) / 2

我不明白的部分来了——我最初的解决方案是这样的:

def sqrt(x: Double) = fixedPoint(z => averageDamp(y => x / y)(z))(1)

但是教授。 Odersky 的解决方案更简洁:

def sqrt(x: Double) = fixedPoint(averageDamp(y => x / y))(1)

我的问题是 - 为什么它有效?根据函数签名,fixedPoint 函数应该接受一个函数 (Double =&gt; Double) 但它不介意传递一个普通的 Double (这是 averageDamp 返回的 - 事实上,如果你尝试显式指定Double的返回类型为averageDamp,编译器不会报错)。

我认为我的方法正确地遵循了类型 - 那么我在这里缺少什么?在哪里指定或暗示(?)averageDamp 返回一个函数,特别是考虑到右侧显然返回一个标量?如何将标量传递给明确只需要函数的函数?您如何推断似乎不支持类型签名的代码?

【问题讨论】:

    标签: scala functional-programming higher-order-functions


    【解决方案1】:

    您的解决方案是正确的,但可以更简洁。

    让我们更仔细地检查averageDamp 函数。

    def averageDamp(f: Double => Double)(x: Double): Double = (x + f(x)) / 2
    

    增加了返回类型注解,使其更加清晰。我想你缺少的是这里:

    但它不介意传递一个普通的 Double (这是 averageDamp 返回的 - 事实上,如果您尝试显式指定 将 Double 类型返回到 averageDamp,编译器不会抛出 错误)。

    但是averageDamp(y =&gt; y/x) 确实返回了Double =&gt; Double 函数! averageDamp 需要传递 两个 参数列表以返回 Double

    如果函数只接收一个参数,它仍然希望完成另一个参数。所以它不是立即返回结果,而是返回一个函数,说“我这里还需要一个参数,喂给我,所以我会返回你想要的”。

    MO 教授确实将 ONE 函数参数传递给它,而不是两个,所以 averageDamp部分应用,因为它返回一个 Double =&gt; Double 函数。

    本课程还将告诉您具有多个参数列表的函数是这样的语法糖形式:

    def f(arg1)(arg2)(arg3)...(argN-1)(argN) = (argN) => f(arg1)(arg2)(arg3)...(argN-1)
    

    如果你给的参数比 f 需要的少,它只返回等式的右边,即一个函数。因此,请注意 averageDamp(y =&gt; x / y),传递给 fixPoint 的参数实际上是一个函数,应该可以帮助您理解问题。

    注意:部分应用函数(或函数柯里化)和多参数列表函数之间存在一些区别

    例如你不能这样声明

    val a = averageDamp(y => y/2)
    

    编译器会抱怨“方法不是部分应用的函数”。

    这里解释了区别:What's the difference between multiple parameters lists and multiple parameters per list in Scala?

    【讨论】:

    • 嗯,那个 SO 线程在我头上,但另一个链接有点帮助。所以,我理解它的方式(如果我错了,请纠正我)是柯里化本质上是一个延迟函数调用 - 延迟到所有 f-args 都被满足(关闭)。在它们出现之前,部分应用的 f 作为结果返回 another f(称为 f'),这样所有不满足的 args 都会形成 f'left-hand 侧并且主体形成f' 的右侧并且没有改变。我仍然无法理解引擎盖下发生的事情 - 谁将标量 arg x 传递给 averageDamp?什么时候?
    • 我想你掌握了柯里化的一般概念。而fixPointx 传递给部分应用的averageDamp,就在val next = f(guess) 行中
    【解决方案2】:

    多个参数列表是返回另一个函数的函数的语法糖。您可以在 scala shell 中看到这一点:

    scala> :t averageDamp _
    (Double => Double) => (Double => Double)
    

    我们可以在没有语法糖的情况下编写相同的函数 - 这就是我们在例如蟒蛇:

    def averageDamp(f: Double => Double): (Double => Double) = {
       def g(x: Double): Double = (x + f(x)) / 2
       g
    }
    

    开始时返回函数可能看起来有点奇怪,但它是作为参数传递函数的补充,并启用了一些非常强大的编程技术。函数只是另一种类型的值,例如 IntString

    在您最初的解决方案中,您重用了变量名y,我认为这会让人有些困惑;我们可以将您写的内容翻译成:

    def sqrt(x: Double) = fixedPoint(z => averageDamp(y => x / y)(z))(1)
    

    通过这个表格,你可以看到模式:

    def sqrt(x: Double) = fixedPoint(z => something(z))(1)
    

    希望现在很明显这与以下内容相同:

    def sqrt(x: Double) = fixedPoint(something)(1)
    

    这是奥德斯基的版本。

    【讨论】:

    • 谢谢,我已经更新了问题而没有重复使用该变量 - 这让我很困惑,但我不知道我可以像这样引入一个新字母。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多