2012-11-21 38 views
8

我一直在谷歌搜索了很長時間,仍然無法找到答案。根據我的理解,如果調用者已將調用封裝在try/catch和/或try/finally塊中,則在.NET 4.5上運行的F#3.0將不會使用遞歸方法的尾遞歸。如果嘗試/捕捉或嘗試/最終在堆棧上的幾個級別是什麼情況?F中的尾遞歸和異常#

+0

如果你運行這樣的功能會發生什麼? –

回答

14

如果你包的一些(尾)身體遞歸函數在try ... with塊則函數不是尾遞歸了,因爲調用幀不能遞歸調用過程中被丟棄 - 它需要留在該堆棧具有註冊的異常處理程序。

例如,假設您有類似iter功能List

let rec iter f list = 
    try 
    match list with 
    | [] ->() 
    | x::xs -> f x; iter f xs 
    with e -> 
    printfn "Failed: %s" e.Message 

當你調用iter f [1;2;3]那麼它會創建一個異常處理4個嵌套堆棧幀(如果你添加rethrowwith分支,那麼它實際上會打印錯誤消息4次)。

您不能真正添加​​異常處理程序而不會中斷尾遞歸。但是,您通常不需要嵌套的異常處理程序。所以最好的解決辦法是重寫功能,使得它不需要在每一個遞歸調用來處理異常:

let iter f list = 
    let rec loop list = 
    match list with 
    | [] ->() 
    | x::xs -> f x; loop xs 
    try loop list 
    with e -> printfn "Failed: %s" e.Message 

這有一點點不同的意義 - 但它不會產生嵌套的異常處理程序和loop尚可完全尾遞歸。

另一種選擇是僅在主體上添加異常處理,不包括尾遞歸調用。實際上,在這個例子中唯一可以拋出異常的是對f的調用;

let rec iter f list = 
    match list with 
    | [] ->() 
    | x::xs -> 
    try 
     f x 
    with e -> 
     printfn "Failed: %s" e.Message 
    iter f xs