2011-09-01 52 views
2

我發現自己偶爾在賦值中編寫循環(遞歸函數)。它使尷尬這樣的代碼:抽象循環

let value = 
    let rec loop a = 
    if ... then a 
    else loop a.B 
    loop a 

我知道我可以移動let外循環約束力,但它的唯一目的是計算限值。

所以我想我可能抽象循環到一個單獨的功能:

let loop f a = 
    let rec aux a = 
    match f a with 
    | Some b -> aux b 
    | None -> a 
    aux a 

話,我可以這樣做:

let value = a |> loop (fun a -> if ... then None else Some a.B) 

也許這就是更好的 - 至少它看起來更像分配比一個函數定義。這裏是我的問題:

  1. 是一個let綁定的代碼氣味的遞歸函數?
  2. 有沒有更好的方法來重構這個?
  3. 如果不是,我的loop函數是否可以進一步推廣,或以某種方式改進?

回答

4

這些問題是有點主觀的,但這裏是我的答案:

  1. 沒有
  2. 我想你得是好的。
  3. 以下是我會做:

    let rec loop guard step init = 
        if guard init then init 
        else loop guard step (step init) 
    
    let value = a |> loop (fun a -> ...) (fun a -> a.B) 
    
+0

Ha。一旦你進一步概括它,你幾乎完成了你開始的地方。 – Daniel

+0

我很難想象編譯後的表示(.NET Reflector在F#代碼中崩潰)。儘管我能夠用ILSpy檢查它。我應該猜到它會將循環移到一個單獨的函數中,只留下一個函數調用。 – Daniel

0

我認爲您或KVB的解決方案是完美的罰款。

我不知道你在循環中迭代的數據結構是什麼。它看起來像一個數據結構的,因此它可能是有意義的實現IEnuemerable<'T>接口或寫它變成IEnumerable<'T>功能:

let rec asSeq a = seq { 
    yield a 
    yield! asSeq a.B } 

然後,你可以只使用Seq.find,並給它的條件需要:

// Using explicit conversion function 
let value = a |> asSeq |> Seq.find (fun a -> ...) 

// If 'a' actually implements 'seq<'T>', it is even nicer: 
let value = a |> Seq.find (fun a -> ...) 
+0

這是一個對象圖,所以,可以將它變成一個序列,儘管它看起來有點矯枉過正。我希望找出一種方法來使這更簡潔。 – Daniel