成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Scala:尾遞歸的跟蹤調用及其局限

開發 后端
本文節選自Martin Odersky,Lex Spoon和Bill Venners所著,Regular翻譯的《Programming in Scala》的第八章。Scala是一種針對 JVM 將函數和面向對象技術組合在一起的編程語言。

在7.2節中,我們提到過想要把更新var的while循環轉換成僅使用val的更函數式風格的話,有時候你可以使用遞歸。下面的例子是通過不斷改善猜測數字來逼近一個值的遞歸函數:

  1. def approximate(guess: Double): Double =  
  2.  if (isGoodEnough(guess)) guess  
  3.  else approximate(improve(guess))  

51CTO編輯推薦:Scala編程語言專題

這樣的函數,帶合適的isGoodEnough和improve的實現,經常用在查找問題中。如果想要approximate函數執行得更快,你或許會被誘惑使用while循環編寫以嘗試加快它的速度,如:

  1. def approximateLoop(initialGuess: Double): Double = {  
  2.  var guess = initialGuess  
  3.  while (!isGoodEnough(guess))  
  4.   guess = improve(guess)  
  5.  guess  
  6. }  

兩種approximate版本哪個更好?就簡潔性和避免var而言,***個,函數式的勝出。但是否指令式的方式或許會更有效率呢?實際上,如果我們測量執行的時間就會發現它們幾乎完全相同!這可能很令人驚奇,因為遞歸調用看上去比簡單的從循環結尾跳到開頭要更花時間。

然而,在上面approximate的例子里,Scala編譯器可以應用一個重要的優化。注意遞歸調用是approximate函數體執行的***一件事。像approximate這樣,在它們***一個動作調用自己的函數,被稱為尾遞歸:tail recursive。Scala編譯器檢測到尾遞歸就用新值更新函數參數,然后把它替換成一個回到函數開頭的跳轉。

道義上你不應羞于使用遞歸算法去解決你的問題。遞歸經常是比基于循環的更優美和簡明的方案。如果方案是尾遞歸,就無須付出任何運行期開銷。

跟蹤尾遞歸函數

尾遞歸函數將不會為每個調用制造新的堆棧框架;所有的調用將在一個框架內執行。這可能會讓檢查程序的堆棧跟蹤并失敗的程序員感到驚奇。例如,這個函數調用自身若干次之后拋出一個異常:

  1. def boom(x: Int): Int =  
  2.  if (x == 0throw new Exception("boom!")  
  3.  else boom(x - 1) + 1 

這個函數不是尾遞歸,因為在遞歸調用之后執行了遞增操作。如果執行它,你會得到預期的:

  1. scala> boom(3)  
  2. java.lang.Exception: boom!  
  3.  at .boom(< console>:5)  
  4.  at .boom(< console>:6)  
  5.  at .boom(< console>:6)  
  6.  at .boom(< console>:6)  
  7.  at .< init>(< console>:6)  
  8. ...  

如果你現在修改了boom從而讓它變成尾遞歸:

  1. def bang(x: Int): Int =  
  2.  if (x == 0throw new Exception("bang!")  
  3.  else bang(x 1)  

你會得到:

  1. scala> bang(5)  
  2. java.lang.Exception: bang!  
  3.  at .bang(< console>:5)  
  4.  at .< init>(< console>:6)  
  5. ...  

這回,你僅看到了bang的一個堆棧框架。或許你會認為bang在調用自己之前就崩潰了,但這不是事實。如果你認為你會在看到堆棧跟蹤時被尾調用優化搞糊涂,你可以用開關項關掉它:

  1. -g:notailcalls 

把這個參數傳給scala的shell或者scalac編譯器。定義了這個選項,你就能得到一個長長的堆棧跟蹤了:

  1. scala> bang(5)  
  2. java.lang.Exception: bang!  
  3.  at .bang(< console>:5)  
  4.  at .bang(< console>:5)  
  5.  at .bang(< console>:5)  
  6.  at .bang(< console>:5)  
  7.  at .bang(< console>:5)  
  8.  at .bang(< console>:5)  
  9.  at .< init>(< console>:6)  
  10. ...  

尾調用優化

approximate的編譯后代碼實質上與approximateLoop的編譯后代碼相同。兩個函數編譯后都是同樣的事三個Java字節碼指令。如果你看一下Scala編譯器對尾遞歸方法,approximate,產生的字節碼,你會看到盡管isGoodEnough和improve都被方法體調用,approximate卻沒有。Scala編譯器優化了遞歸調用:

  1. public double approximate(double);  
  2.  Code:  
  3.   0: aload_0  
  4.   1: astore_3  
  5.   2: aload_0  
  6.   3: dload_1  
  7.   4: invokevirtual #24//Method isGoodEnough:(D)Z  
  8.   7: ifeq 12 
  9.   10: dload_1  
  10.   11: dreturn  
  11.   12: aload_0  
  12.   13: dload_1  
  13.   14: invokevirtual #27//Method improve:(D)D  
  14.   17: dstore_1  
  15.   18goto 2 

尾遞歸的局限

Scala里尾遞歸的使用局限很大,因為JVM指令集使實現更加先進的尾遞歸形式變得很困難。Scala僅優化了直接遞歸調用使其返回同一個函數。如果遞歸是間接的,就像在下面的例子里兩個互相遞歸的函數,就沒有優化的可能性了:

  1. def isEven(x: Int): Boolean =  
  2.  if (x == 0true else isOdd(x - 1)  
  3. def isOdd(x: Int): Boolean =  
  4.  if (x == 0false else isEven(x - 1)  

同樣如果***一個調用是一個函數值你也不能獲得尾調用優化。請考慮下列遞歸代碼的實例:

  1. val funValue = nestedFun _  
  2. def nestedFun(x: Int) {  
  3.  if (x != 0) { println(x); funValue(x - 1) }  
  4. }  

funValue變量指向一個實質是包裝了nestedFun的調用的函數值。當你把這個函數值應用到參數上,它會轉向把nestedFun應用到同一個參數,并返回結果。因此你或許希望Scala編譯器能執行尾調用優化,但在這個例子里做不到。因此,尾調用優化受限于方法或嵌套函數在***一個操作調用本身,而沒有轉到某個函數值或什么其它的中間函數的情況。(如果你還不能完全明白尾遞歸,參見8.9節)。

【相關閱讀】

  1. Scala允許的重復參數
  2. 學習Scala的閉包
  3. Scala的偏應用函數
  4. Scala:函數文本的短格式和占位符語法
  5. 介紹Scala的***類函數

責任編輯:book05 來源: Artima
相關推薦

2020-05-27 07:38:36

尾遞歸優化遞歸函數

2020-09-30 08:07:46

如何優化尾調用

2009-07-21 07:30:00

Scala程序Application

2010-09-17 13:01:44

Python

2017-07-12 10:00:22

深度學習小數據樣本深度網絡

2017-07-11 15:25:53

深度學習人工智能

2013-08-20 09:23:06

Scala遞歸

2024-03-12 09:43:45

2009-12-15 11:05:05

2019-03-26 08:15:45

iOS尾調用Objective-C

2009-11-27 16:20:22

PHP遞歸調用

2024-05-08 08:00:00

2011-04-25 16:35:06

Linux調用

2009-08-06 18:02:22

存儲過程

2009-07-08 12:43:59

Scala ServlScala語言

2010-09-14 15:34:41

Scala

2014-04-16 10:54:45

Javascript遞歸調用

2020-10-31 17:33:18

Scala語言函數

2023-12-04 07:09:53

函數遞歸python

2021-03-24 10:00:32

Python遞歸函數Python基礎
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人午夜影院 | 国产精品毛片一区二区在线看 | 性一交一乱一伦视频免费观看 | 精久久久 | 欧美中文一区 | 丝袜 亚洲 另类 欧美 综合 | 成人精品在线 | 久久久精品一区二区三区 | 国产亚洲欧美日韩精品一区二区三区 | 国产一级视频 | 国产精品久久久久久久粉嫩 | 青青草原综合久久大伊人精品 | 国产精品激情在线 | 午夜影院中文字幕 | 欧美日韩在线播放 | 精品久久久久久久久久久久久久 | 国产精品久久影院 | 日韩aⅴ片| 日韩精品免费视频 | 久久久久久久一区 | 成人一区二区在线 | 久久久999国产精品 中文字幕在线精品 | 国产精品揄拍一区二区久久国内亚洲精 | 在线中文字幕日韩 | 色久在线 | 欧美一区二区三区免费电影 | 99精品网站 | 99re在线视频 | av在线伊人| 亚洲精品在线视频 | 综合久久色 | 蜜桃视频麻豆 | 成人a视频片观看免费 | www.天天操 | 天堂男人av | 综合国产 | 国产日韩欧美另类 | 欧美激情免费在线 | 久久久久久久久久久久久91 | 欧美二区三区 | 国产在线一区观看 |