混合DU和其他值时的F#模式匹配

时间:2010-04-23 07:57:59

标签: f# pattern-matching

表达以下代码的最有效方法是什么?

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返回一个布尔结果,其中false应该返回null 并且true应该再次运行整个块(它包含在一个名为next的func中) 或者如果找到break的特殊值,则循环应退出并返回中断值。

有没有办法将该代码块压缩为更小的代码?

6 个答案:

答案 0 :(得分: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

答案 1 :(得分:3)

我想指出,Eval的结果类型似乎有一个子类型层次结构,如果它也是DU,那么你可以做类似的事情

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

为嵌套模式寻求帮助。

答案 2 :(得分: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

答案 3 :(得分:1)

我最终为此创建了一个活动模式。 其他地方存在类似的逻辑,因此我可以将其重复使用

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

看起来不错?

答案 4 :(得分: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 

编辑:正如Roger在评论中指出的那样,这个版本的代码可能不太可读。我认为更好的选择是仅使用TryCast并将原始代码格式略有不同(尽管这不是完全标准的缩进,它是正确的,F#编译器处理得很好):

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

这可能是基于模式匹配的最易读的选项,但您也可以使用kvb版本中第一个if的{​​{1}}个instad,并将其与match结合使用(这真的取决于个人喜好):

TryCast

无论如何,我相信if cond.EvalBool() then match body.Eval() with | TryCast(Break(scope) as e) -> e :> obj | _ -> next() else null 使代码更具可读性,因为您避免了一个嵌套(由于TryCast而需要其他嵌套)。

答案 5 :(得分: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