前面提到過,如果想把更新var的while迴圈轉換成僅使用val這種更函式式的風格的話,有時候你可以使用遞迴。下面的例子是通過不斷改善猜測數字來逼近乙個值的遞迴函式:
var guess = initialguess
while(!isgoodenough(guess)) guess = improve(guess)
guess
事實上你不必刻意迴避使用遞迴演算法去解決問題。遞迴經常是比基於迴圈的更優美和簡明的方案。如果方案是尾遞迴,就無須付出任何執行期開銷。
尾遞迴函式追蹤
尾遞迴函式將不會為每個呼叫製造新的堆疊結構;所有的呼叫將在乙個結構內執行。這可能會讓檢查程式堆疊跟蹤資訊並失敗的程式設計師感到驚奇。例如,這個函式呼叫自身若干次之後丟擲了異常:
def boom(x:int):int = {
if(x == 0) throw new exception("boom!")
else boom(x - 1) + 1
這個函式不是尾遞迴,因為在遞迴呼叫之後執行了遞增操作。如果執行:
boom(3)
將會得到預期的:
如果你現在修改了boom從而讓它變成尾遞迴:
def bang(x:int):int =
if(x == 0) throw new exception("bang!")
else bang(x - 1)
執行:bang(5)
你會得到:
這回,你僅看到了bang的乙個堆疊結構。或許你會認為bang在呼叫自己之前就崩潰了,但事實並非如此。如果你認為你會在看到椎棧跟蹤時被尾呼叫優化搞糊塗,你可以用開關項關掉它:-g:notailcalls
把這個引數傳給scala的shell或者scalac編譯器。定義了這個選項,你就能看到乙個長長的堆疊跟蹤。例如,我們在scala的shell中輸入:
d:\> scala -g:notailcalls
然後輸入:
def bang(x:int):int =
if(x == 0) throw new exception("bang!")
else bang(x - 1)
bang(5)
得到結果:
這就是放棄尾遞迴呼叫優化後的結果。
尾遞迴的侷限
scala裡尾遞迴的使用侷限很大,因為jvm指令集使實現更加先進的尾遞迴形式變得很困難。scala僅優化了直接遞迴呼叫使其返回同乙個函式。如果遞迴是間接的,就像在下面的例子裡兩個互相遞迴的函式,就沒有優化的可能性了:
def iseven(x:int):boolean =
if(x == 0) true else isodd(x - 1)
def isodd(x:int):boolean =
if(x == 0) false else iseven(x - 1)
同樣,如果最後乙個呼叫是乙個函式值你也不能獲得尾呼叫優化。請考慮下列遞迴**的實現:
val fuvalue = nestedfun _
def nestedfun(x:int) {
if(x != 0) {
println(x)
funvalue(x - 1)
funvalue變數指向乙個實質是包裝了nestedfun的呼叫的函式值。當你把這個函式值應用到引數上,它會轉向把nestedfun應用到同乙個引數,並返回結果。因此你或許希望scala編譯器能執行尾呼叫優化,但在這個例子裡做不到。因此,尾呼叫優化限定了方法或巢狀函式必須在最後乙個操作呼叫本身,而不是轉到某個函式值或什麼其他的中間函式的情況。
python遞迴 返回 閉包 函式
函式呼叫函式自身,這種方式稱為遞迴,這種函式稱為遞迴函式 遞迴函式的優點是定義簡單,邏輯清晰,缺點是過深的呼叫會導致棧溢位 遞迴函式使用過程中,需要找到不變的規律和停止遞迴的邊界條件,因為函式自身呼叫自身,函式本身的結構不變,只是每次傳的引數改變啦 遞迴實現過程 擴充套件 利用棧的思想,先找邊界條件...
python匿名函式和閉包函式
一 匿名函式 在定義函式的時候,不想給函式起乙個名字。這個時候就可以用lambda來定義乙個匿名函式。語法 變數名 lambda 引數 表示式 引數 可選,通常以逗號分隔 表示式 不能包含迴圈 return,但是可以包含if.else.返回值就是該表示式的結果。如 建立乙個帶引數的匿名函式 add ...
python函式物件和閉包函式
函式物件 函式物件指的是函式可以被當做 資料 來處理,具體可以分為四個方面的使用,我們如下 函式可以被引用 def add x,y return x y func add func 1,2 3 函式可以作為容器型別的元素 dic dic dic add 1,2 3 函式可以作為引數傳入另外乙個函式 ...