2009-04-30 60 views
15

我寫的follwing功能:我怎麼知道,如果一個功能是在F#尾遞歸

let str2lst str = 
    let rec f s acc = 
     match s with 
     | "" -> acc 
     | _ -> f (s.Substring 1) (s.[0]::acc) 
    f str [] 

我怎麼能知道F#編譯器把它變成一個循環?有沒有辦法找到沒有使用反射器(我沒有反射器的經驗,我不知道C#)?

編輯:另外,是否有可能編寫一個尾遞歸函數而不使用內部函數,或者是否需要循環駐留?

另外,F#std lib中是否有函數運行給定的函數多次,每次都將它的最後輸出作爲輸入?比方說,我有一個字符串,我想要在字符串上運行一個函數,然後再通過結果字符串運行它,等等......

+0

另請參見http://stackoverflow.com/questions/5809683/is-my-rec-function-tail-recursive – Brian 2011-04-27 22:16:44

回答

22

不幸的是,沒有無足輕重的方法。

閱讀源代碼並使用類型並通過檢查確定是否是尾部調用(它是'最後一件事',而不是'嘗試'塊)並不難,但是第二個人自作聰明,犯錯誤。沒有簡單的自動化方法(除了檢查生成的代碼之外)。

當然,你可以在大量的測試數據上試試你的功能,看看它是否爆炸。

F#編譯器將爲所有尾調用生成尾尾IL指令(除非使用編譯器標誌關閉它們 - 用於當您想保留堆棧幀進行調試時),除了直接尾遞歸函數將被優化成循環。 (編輯:我認爲現在的F#編譯器也無法發出.tail的情況下,它可以證明沒有通過這個調用站點的遞歸循環;這是一個優化,因爲.tail操作碼在很多平臺上稍微慢一點。)

'tailcall'是一個保留的關鍵字,其想法是未來的F#版本可能允許您編寫eg

tailcall func args 

然後如果不是尾巴呼叫,則會發出警告/錯誤。

只有不是自然的尾遞歸(因此需要一個額外的累加器參數)的函數會'強制'你進入'內部函數'的習慣用法。

這裏是你問一個代碼示例:

let rec nTimes n f x = 
    if n = 0 then 
     x 
    else 
     nTimes (n-1) f (f x) 

let r = nTimes 3 (fun s -> s^" is a rose") "A rose" 
printfn "%s" r 
+0

「tailcall」很有趣,因爲它指定了呼叫站點而不是可能是對整個功能更有用的「tailrec」聲明。你可以有一個「部分尾遞歸」的功能嗎? – 2010-06-05 14:22:39

2

我喜歡憑經驗保羅·格雷厄姆在制定上Lisp的:如果有工作剩下要做,例如操縱遞歸調用輸出,那麼調用不是尾遞歸。

相關問題