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

qua*_*tum 4 functional-programming scala 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)
  }
Run Code Online (Sandbox Code Playgroud)

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

def sqrt(x: Double) = fixedPoint(y => x / y)(1)
Run Code Online (Sandbox Code Playgroud)

因为这样的方法振荡(因此,sqrt(2)结果将无限期地在1.0和2.0之间交替)被挫败.

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

def sqrt(x: Double) = fixedPoint(y => (y + x / y) / 2)(1)
Run Code Online (Sandbox Code Playgroud)

最后,我们介绍averageDamp函数,任务是sqrtfixedPoint和写averageDamp.的averageDamp定义如下:

def averageDamp(f: Double => Double)(x: Double) = (x + f(x)) / 2
Run Code Online (Sandbox Code Playgroud)

这是我不理解的部分 - 我最初的解决方案是:

def sqrt(x: Double) = fixedPoint(z => averageDamp(y => x / y)(z))(1)
Run Code Online (Sandbox Code Playgroud)

教授.奥德斯基的解决方案更简洁:

def sqrt(x: Double) = fixedPoint(averageDamp(y => x / y))(1)
Run Code Online (Sandbox Code Playgroud)

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

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

Her*_*lme 6

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

让我们averageDamp更仔细地审视这个功能.

def averageDamp(f: Double => Double)(x: Double): Double = (x + f(x)) / 2
Run Code Online (Sandbox Code Playgroud)

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

但它不介意传递一个普通的Double(这是averageDamp返回的 - 实际上,如果你试图显式指定Double to averageDamp的返回类型,编译器不会抛出错误).

averageDamp(y => y/x)确实会返回一个Double => Double功能!averageDamp需要传递两个参数列表才能返回a Double.

如果函数只接收一个参数,它仍然希望完成另一个参数.因此,它不是立即返回结果,而是返回一个函数,说"我仍然需要一个参数,给我这个,所以我会返回你想要的".

莫教授确实通过ONE函数的参数给它,而不是两个,因此averageDamp部分应用,因为它返回一个感觉Double => Double功能.

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

def f(arg1)(arg2)(arg3)...(argN-1)(argN) = (argN) => f(arg1)(arg2)(arg3)...(argN-1)
Run Code Online (Sandbox Code Playgroud)

如果你给出一个少于f需要的参数,它只返回方程的右边,即一个函数.因此,注意到,averageDamp(y => x / y)传递给它的论证fixPoint实际上是一个函数应该可以帮助你理解这个问题.

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

例如,你不能这样声明

val a = averageDamp(y => y/2)
Run Code Online (Sandbox Code Playgroud)

编译器会抱怨这个'方法不是部分应用的函数'.

这里解释了不同之处:Scala中每个列表的多个参数列表和多个参数之间的区别是什么?.