scala是否在函数签名中忽略了类型?

时间:2014-10-09 04:46:22

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

我正在参加优秀的Martin Odersky的FP课程讲座,其中一个讲座通过Newton的方法演示高阶函数,以找到某些函数的固定点。在讲座中有一个重要的步骤,我认为类型签名被违反,所以我会要求解释。 (对于介入的长篇介绍道歉 - 它觉得有必要。)

实现这种算法的一种方法是这样的:

  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函数,任务是使用sqrtfixedPoint编写averageDampaverageDamp定义如下:

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

以下是我不明白的部分 - 我的初步解决方案是:

def sqrt(x: Double) = fixedPoint(z => averageDamp(y => x / y)(z))(1)
但是教授。奥德斯基的解决方案更简洁:

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

我的问题是 - 它为什么有效?根据函数签名,fixedPoint函数应该是一个函数(Double => Double),但它并不介意传递一个普通的Double(这是averageDamp返回的 - 在事实上,如果您尝试将Double的返回类型显式指定为averageDamp,则编译器不会抛出错误。)

我认为我的方法正确地遵循了类型 - 所以我在这里缺少什么?指定或隐含(?)averageDamp返回一个函数的位置,特别是在右边显然返回一个标量的情况下?如何将标量传递给仅明确需要函数的函数?你怎么推理似乎不尊重类型签名的代码?

2 个答案:

答案 0 :(得分:6)

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

让我们更仔细地审查averageDamp功能。

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

添加返回类型注释以使其更清晰。我想你缺少的是:

  

但它并不介意传递普通的Double(这是averageDamp返回的 - 事实上,如果你试图明确指定   返回类型Double to averageDamp,编译器不会抛出一个   误差)。

但是averageDamp(y => y/x)会返回Double => Double函数! averageDamp需要传递两个参数列表才能返回Double

如果函数只接收一个参数,它仍然希望完成另一个参数。因此,它不是立即返回结果,而是返回一个函数,说'#34;我仍然需要一个参数,喂我,所以我会返回你想要的东西&#34;。

MO教授确实将 ONE 函数参数传递给它,而不是两个,因此averageDamp 部分应用,因为它返回{{1功能。

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

Double => Double

如果你给出一个少于f需要的参数,它只返回方程的右边,即一个函数。因此,注意def f(arg1)(arg2)(arg3)...(argN-1)(argN) = (argN) => f(arg1)(arg2)(arg3)...(argN-1) ,传递给averageDamp(y => x / y)的参数实际上是一个函数,可以帮助您理解这个问题。

注意:部分应用函数(或函数currying)与多参数列表函数之间存在一些差异

例如,你不能像这样声明

fixPoint

编译器会抱怨这一点,因为&#39;方法不是部分应用的功能&#39;。

这里解释了不同之处:What's the difference between multiple parameters lists and multiple parameters per list in Scala?

答案 1 :(得分:1)

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

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

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

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)

这是奥德斯基的版本。