日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Scala:尾遞歸的跟蹤調(diào)用及其局限

在7.2節(jié)中,我們提到過想要把更新var的while循環(huán)轉(zhuǎn)換成僅使用val的更函數(shù)式風(fēng)格的話,有時(shí)候你可以使用遞歸。下面的例子是通過不斷改善猜測數(shù)字來逼近一個(gè)值的遞歸函數(shù):

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

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

這樣的函數(shù),帶合適的isGoodEnough和improve的實(shí)現(xiàn),經(jīng)常用在查找問題中。如果想要approximate函數(shù)執(zhí)行得更快,你或許會被誘惑使用while循環(huán)編寫以嘗試加快它的速度,如:

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

兩種approximate版本哪個(gè)更好?就簡潔性和避免var而言,***個(gè),函數(shù)式的勝出。但是否指令式的方式或許會更有效率呢?實(shí)際上,如果我們測量執(zhí)行的時(shí)間就會發(fā)現(xiàn)它們幾乎完全相同!這可能很令人驚奇,因?yàn)檫f歸調(diào)用看上去比簡單的從循環(huán)結(jié)尾跳到開頭要更花時(shí)間。

然而,在上面approximate的例子里,Scala編譯器可以應(yīng)用一個(gè)重要的優(yōu)化。注意遞歸調(diào)用是approximate函數(shù)體執(zhí)行的***一件事。像approximate這樣,在它們***一個(gè)動作調(diào)用自己的函數(shù),被稱為尾遞歸:tail recursive。Scala編譯器檢測到尾遞歸就用新值更新函數(shù)參數(shù),然后把它替換成一個(gè)回到函數(shù)開頭的跳轉(zhuǎn)。

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

跟蹤尾遞歸函數(shù)

尾遞歸函數(shù)將不會為每個(gè)調(diào)用制造新的堆??蚣埽凰械恼{(diào)用將在一個(gè)框架內(nèi)執(zhí)行。這可能會讓檢查程序的堆棧跟蹤并失敗的程序員感到驚奇。例如,這個(gè)函數(shù)調(diào)用自身若干次之后拋出一個(gè)異常:

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

這個(gè)函數(shù)不是尾遞歸,因?yàn)樵谶f歸調(diào)用之后執(zhí)行了遞增操作。如果執(zhí)行它,你會得到預(yù)期的:

 
 
 
  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. ...  

如果你現(xiàn)在修改了boom從而讓它變成尾遞歸:

 
 
 
  1. def bang(x: Int): Int =  
  2.  if (x == 0) throw 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的一個(gè)堆棧框架?;蛟S你會認(rèn)為bang在調(diào)用自己之前就崩潰了,但這不是事實(shí)。如果你認(rèn)為你會在看到堆棧跟蹤時(shí)被尾調(diào)用優(yōu)化搞糊涂,你可以用開關(guān)項(xiàng)關(guān)掉它:

 
 
 
  1. -g:notailcalls 

把這個(gè)參數(shù)傳給scala的shell或者scalac編譯器。定義了這個(gè)選項(xiàng),你就能得到一個(gè)長長的堆棧跟蹤了:

 
 
 
  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. ...  

尾調(diào)用優(yōu)化

approximate的編譯后代碼實(shí)質(zhì)上與approximateLoop的編譯后代碼相同。兩個(gè)函數(shù)編譯后都是同樣的事三個(gè)Java字節(jié)碼指令。如果你看一下Scala編譯器對尾遞歸方法,approximate,產(chǎn)生的字節(jié)碼,你會看到盡管isGoodEnough和improve都被方法體調(diào)用,approximate卻沒有。Scala編譯器優(yōu)化了遞歸調(diào)用:

 
 
 
  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.   18: goto 2 

尾遞歸的局限

Scala里尾遞歸的使用局限很大,因?yàn)镴VM指令集使實(shí)現(xiàn)更加先進(jìn)的尾遞歸形式變得很困難。Scala僅優(yōu)化了直接遞歸調(diào)用使其返回同一個(gè)函數(shù)。如果遞歸是間接的,就像在下面的例子里兩個(gè)互相遞歸的函數(shù),就沒有優(yōu)化的可能性了:

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

同樣如果***一個(gè)調(diào)用是一個(gè)函數(shù)值你也不能獲得尾調(diào)用優(yōu)化。請考慮下列遞歸代碼的實(shí)例:

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

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

【相關(guān)閱讀】

  1. Scala允許的重復(fù)參數(shù)
  2. 學(xué)習(xí)Scala的閉包
  3. Scala的偏應(yīng)用函數(shù)
  4. Scala:函數(shù)文本的短格式和占位符語法
  5. 介紹Scala的***類函數(shù)

本文題目:Scala:尾遞歸的跟蹤調(diào)用及其局限
本文路徑:http://m.5511xx.com/article/djejpoh.html