2014-10-09 48 views
4

我正在通過優秀的馬丁奧德斯基的FP課程講課,其中一個講座通過牛頓的方法來演示高階函數,以找出某些函數的固定點。在我認爲類型簽名被侵犯的講座中有一個迫切的步驟,所以我會要求解釋。 (道歉長介紹這是入站 - 它認爲這是必要的。)實現這樣的算法是這樣給出scala在函數簽名中是否會破壞類型?

方式一:

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

接下來,我們試着計算通過定點平方根功能,而是通過

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功能和任務是用fixedPointaverageDampsqrt。該averageDamp定義如下:

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 => Double),但它並不介意傳遞一個普通的Double(這就是averageDamp返回的值 - 事實上,如果試圖顯式指定返回類型Double averageDamp,編譯器不會拋出錯誤)。

我認爲我的方法正確地遵循類型 - 所以我在這裏錯過了什麼?它在哪裏指定或暗示(?)averageDamp返回一個函數,特別是在右邊明顯返回一個標量的情況下?你怎麼能把一個標量傳遞給一個明確期望函數的函數呢?你如何推理似乎不符合類型簽名的代碼?

回答

6

您的解決方案是正確的,但它可以更簡潔。

讓我們仔細檢查averageDamp函數。

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

添加返回類型註釋使其更清楚。我認爲你缺少的是在這裏:

但它並不介意被通過一個普通雙人間(這是什麼averageDamp返回 - 事實上,如果你試圖明確指定雙到averageDamp的 返回類型,編譯器不會拋出 錯誤)。

averageDamp(y => y/x)確實會返回一個Double => Double函數! averageDamp需要通過TWO參數列表返回Double

如果函數只接收一個參數,它仍然希望另一個參數完成。因此,不是立即返回結果,而是返回一個函數,說「我在這裏仍然需要一個參數,餵我這個,所以我會返回你想要的」。

MO教授確實通過ONE函數參數到它,而不是兩個,所以averageDamp部分地施加,在它返回一個Double => Double功能的讀出。

課程也會告訴你有多個參數列表功能是本語法糖形式:

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

如果你給一個大於f需求少的說法,它只是迴歸方程右邊,也就是,一個函數。所以,請注意averageDamp(y => x/y),這個參數傳遞給fixPoint,實際上是一個函數應該可以幫助你理解這個問題。

通知:有部分應用功能(或功能柯里)和多個參數列表功能

之間的一些差異例如,你不能聲明這樣

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

編譯器會抱怨這因爲「方法不是部分應用功能」。

區別在這裏解釋:What's the difference between multiple parameters lists and multiple parameters per list in Scala?

+0

這個鏈接應該有所幫助http://www.codecommit.com/blog/scala/function-currying-in-scala – 2014-10-09 05:29:25

+0

嗯,那個線程已經超過我的腦袋,但另一個鏈接有點幫助。所以,我理解它的方式(如果我錯了,請糾正我)是因爲currying基本上是一個延遲函數調用 - 推遲到所有f-args被滿足(關閉)爲止。在他們之前,部分應用的f返回結果爲_another_ f(稱爲'f''),使得所有不滿意的參數形成'f''的**左手**側,而身體形成右手'f''的一側並沒有改變。我仍然無法理解引擎蓋下發生了什麼 - 誰將標量arg'x'傳遞給'averageDamp'?什麼時候? – quantum 2014-10-10 03:21:05

+0

我認爲你掌握了柯里裏的一般想法。 'fixPoint'將'x'部分應用於'averageDamp',就在'val next = f(guess)' – 2014-10-10 11:14:00

1

多參數列表是函數的語法糖,它返回另一個函數。您可以在斯卡拉殼看到這一點:

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) 

這是Odersky的版本。

+0

這一行''''''''謝謝,我已經更新了這個問題,但沒有重用變量 - 它讓我困惑,我不知道我可以像這樣介紹一封新信。 – quantum 2014-10-10 03:17:29