代码之家  ›  专栏  ›  技术社区  ›  Roger Johansson

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

  •  2
  • Roger Johansson  · 技术社区  · 14 年前

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

    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
    

    如果为true,则应再次运行整个块(将其包装在名为next的func中) 或者如果找到break的特殊值,则循环应该退出并返回break值。

    有没有办法把那块代码压缩成更小的?

    6 回复  |  直到 14 年前
        1
  •  4
  •   kvb    14 年前

    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
    
        2
  •  3
  •   Brian    14 年前

    我想指出的是,对于 Eval ,如果这也是一个DU,那么你可以这样做

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

    嵌套模式万岁。

        3
  •  2
  •   Sebastian Ullrich    14 年前

    我不太喜欢匹配布尔值而不是使用 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#s 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
    
        4
  •  1
  •   Roger Johansson    14 年前

    我最终为此创建了一个活动模式。 其他地方也有类似的逻辑,所以我可以使它可重用

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

        5
  •  1
  •   Tomas Petricek    14 年前

    使嵌套的 match 构造时,需要使用嵌套模式。这对有歧视的联合最有效(正如Brian所指出的,我同意设计F代码来使用主要有歧视的联合是你能做的最好的事情)。

    否则,如果您想使用 比赛 (ssp发布了一个示例,其中显示了专门针对您的问题的活动模式)。但是,您可以使用以下两种可重用的活动模式来实现这一点:

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

    第一个是 :? ,但它允许嵌套其他模式以匹配该值(这在 as

    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()
    

    这可能是基于模式匹配的最可读的选项,但是您也可以使用 if 第一站 比赛 与kvb版本相同,并将其与 (这实际上取决于个人喜好):

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

    无论如何,我相信 TryCast公司 使代码更可读,因为您避免了一个嵌套(这是因为 :? .. as ..

        6
  •  0
  •   ssp    14 年前

    如果你的意思是“最有效的方式”作为最短代码,我也投票给美联社:

    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