2010-04-23 57 views
2

表達以下代碼的最有效方法是什麼?混合DU和其他值時的F#模式匹配

match cond.EvalBool() with 
| true ->     
    match body.Eval() with 
    | :? ControlFlowModifier as e -> 
     match e with 
     | Break(scope) -> e :> obj //Break is a DU element of ControlFlowModifier 
     | _ -> next() //other members of CFM should call next() 
    | _ -> next() //all other values should call next() 
| false -> null 

cond.EvalBool返回boolean的結果,其中假應該返回null 和真應該要麼再次運行整個塊(其裹在一個叫做未來FUNC) 或者休息的特殊價值被發現,然後該循環應該退出並返回中斷值。

有什麼辦法可以將代碼塊壓縮到更小的東西嗎?

+0

不是你的問題的答案,但是,如果你的代碼看起來像這樣(使用':?'和'null'),那麼你可能會考慮一個更習慣的F#解決方案或包裝器。 – 2012-12-16 21:18:48

回答

4

我認爲你寫的代碼很好。下面是我略微傾向於一種替代方案:

let isBreak = function | Break(_) -> true | _ -> false 
if cond.EvalBool() then 
    match body.Eval() with 
    | :? ControlFlowModifier as e when isBreak e -> e :> obj 
    | _ -> next() 
else 
    null 
+0

嗯,你打我:) – 2010-04-23 14:54:21

+0

@Kha - 它的奇怪之處在於我們的方法有多相似。實際上,我也打算使用'box e'而不是'e:> obj',但是我們認爲它完全是一種個人風格的東西... – kvb 2010-04-23 15:01:51

2

我不是太喜歡匹配布爾值,而不是使用if-else的。約

let isBreak = function Break _ -> true | _ -> false 
... 

if cond.EvalBool() then 
    match body.Eval() with 
    | :? ControlFlowModifier as e when isBreak e -> box e 
    | _ -> next() 
else null 

或者,如果你認爲特別isBreak功能不應該是必要的(我明白)什麼,讓我們嘗試創造一個更一般的功能:C#的as操作

let tryCast<'T> (o : obj) = 
    match o with 
    | :? 'T as x -> Some x 
    | _ -> None 
... 

if cond.EvalBool() then 
    match body.Eval() |> tryCast with 
    | Some (Break _ as e) -> box e //Break is a DU element of ControlFlowModifier 
    | _ -> next() //all other values should call next() 
else null 
1

我最終爲此創建了一個活躍的模式。 同樣的邏輯也存在於其他地方,所以我可以使其可重複使用

let rec next() : obj = 
if cond.EvalBool() then 
    match body.Eval() with 
    | IsBreak(res) -> res 
    | _ -> step.Eval() |> ignore ; next() 
else null 

看起來體面?

3

我想指出的是,它似乎有一個爲結果類型的Eval亞型層次,如果不是那也是一個DU,那麼你可以做這樣的事情

match body.Eval() with 
| ControlFlowModifier(Break e) -> box e 
| _ -> next() 

華友世紀嵌套模式。

+0

是的,嵌套模式很棒。當你開始引入活躍模式時更是如此。 – gradbot 2010-04-23 16:55:12

+0

嗯,它似乎沒有工作把DU類型的名稱和DU元素裏面呢? 無法讓它編譯 – 2010-04-23 19:50:19

+0

這假定body.Eval()返回一個類型爲Foo的值,它是一個DU,它有一個「ControlFlowModifier of Bar」的情況,其中Bar也是一個DU,它有一個「Break of Scope」管他呢。 – Brian 2010-04-23 21:57:59

0

如果你的意思是「最有效的方法」爲最短的代碼,我投給AP太:

let (|CondEval|_|) (c,_) = if c.EvalBool() then Some true else None 
let (|BodyEval|_|) (_,b) = 
    match b.Eval() with 
    | ControlFlowModifier as e -> Some e 
    | _ -> None 

match cond,body with 
| CondEval _ & BodyEval e -> e :> obj 
| true -> next() 
| false -> null 
1

爲了展平嵌套match結構,你需要使用嵌套模式。這對歧視工會最有效(正如Brian所指出的那樣 - 我同意設計F#代碼來主要使用歧視工會是你能做的最好的事情)。

否則,如果您想使用match(ssp發佈一個示例,其中顯示專門針對您的問題的活動模式)簡潔地編寫代碼,您需要一些活動模式。但是,您可以使用以下兩種可重複使用的活動模式做到這一點:

let (|TryCast|_|) a : 'res option =  
    match (box a) with 
    | :? 'res as r -> Some(r) 
    | _ -> None 

let (|Value|) (l:Lazy<_>) = l.Value 

第一種是像:?,但它可以讓你嵌套其他模式相匹配的值(這是不可能的as)。第二個強制評估懶惰值(我想這兩個都可以在F#庫中聲明,因爲它們非常有用)。現在,你可以寫:

match lazy cond.EvalBool(), lazy body.Eval() with 
| Value(true), Value(TryCast((Break(scope) : ControlFlowModifier)) as e) -> 
    e :> obj //Break is a DU element of ControlFlowModifier 
| Value(true), _ -> 
    next() //all other values should call next() 
| _, _ -> null 

編輯:羅傑在評論中指出,這個版本的代碼可能不是很可讀。我認爲一個更好的選擇是隻使用TryCast和略有不同格式化你的原代碼(雖然這不完全是標準的縮進,這是正確的,F#編譯器處理它精細):

match cond.EvalBool() with 
| false -> null 
| true ->     
match body.Eval() with 
| TryCast(Break(scope) as e) -> e :> obj 
| _ -> next() 

這可能是基於最可讀的選項模式匹配,但你也可以通過KVB使用if instad第一match作爲版本,並與TryCast結合起來(這完全取決於個人喜好):

if cond.EvalBool() then 
    match body.Eval() with 
    | TryCast(Break(scope) as e) -> e :> obj 
    | _ -> next() 
else null 

在任何情況下,我相信TryCast使代碼更具可讀性,因爲您避免了一個嵌套(由於:? .. as ..而需要其他嵌套)。

+0

儘管我發現這種方法技術上很有趣,但代碼的可讀性幾乎消失了(IMO)。 – 2010-04-24 15:03:28

+0

@Roger:是的,我確實認爲原來的代碼已經可以。也許只使用'TryCast'將是最好的選擇。 – 2010-04-24 15:28:30