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

发布于 2024-08-30 11:19:47 字数 576 浏览 7 评论 0原文

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

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 的函数中) 或者如果找到了特殊的break值,那么循环应该退出并返回break值。

有什么方法可以将该代码块压缩为更小的代码吗?

What would be the most effective way to express the following code?

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 returns a boolean result where false should return null
and true should either run the entire block again (its wrapped in a func called next)
or if the special value of break is found, then the loop should exit and return the break value.

Is there any way to compress that block of code to something smaller?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

长安忆 2024-09-06 11:19:47

我认为你写的代码很好。这是我稍微喜欢的另一种选择:

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

I think that the code that you have written is fine. Here's an alternative which I marginally prefer:

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
救星 2024-09-06 11:19:47

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

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

对嵌套模式执行类似 Hurray 的操作。

I want to point out that it appears there's a subtype hierarchy for the result type of Eval, and if instead that were also a DU, then you could do something like

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

Hurray for nested patterns.

2024-09-06 11:19:47

我不太喜欢匹配布尔值而不是使用 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

I'm not too fond of matching booleans instead of using if-else. What about

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

Or, if you think that special isBreak function shouldn't be necessary (I'd understand that), lets try creating a more general function: C#'s as operator

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
迷爱 2024-09-06 11:19:47

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

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

看起来不错吗?

I ended up creating an active pattern for this.
Similar logic exist elsewhere so I could make it reusable

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

Looks decent?

べ繥欢鉨o。 2024-09-06 11:19:47

要展平嵌套的 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  

第一个类似于 :?,但它允许您嵌套其他模式以匹配该值(这对于 是不可能的) >如)。第二个强制评估惰性值(我认为它们都可以在 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()

这可能是基于模式匹配的最可读选项,但您也可以使用 if 代替第一个 match,如 kvb 版本中所示,并将其与 TryCast 结合使用> (这实际上取决于个人喜好):

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

无论如何,我相信 TryCast 使代码更具可读性,因为您避免了一次嵌套(这是其他需要的,因为 :? .. ..)。

To flatten the nested match constructs, you'll need to use nested patterns. This works best for discriminated unions (as pointed out by Brian - and I agree that designing F# code to use primarily discriminated unions is the best thing you can do).

Otherwise, you'll need some active patterns if you want to write the code succinctly using match (ssp posted one example, which shows active patterns specifically for your problem). However, you can do this using the following two reusable active patterns:

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

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

The first one is like :?, but it allows you to nest other patterns to match the value (which isn't possible with as). The second one forces evaluation of lazy value (I suppose that both of them could be declared in F# libraries as they are quite useful). Now you can write:

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 

EDIT: As Roger pointed out in a comment, this version of the code may not be very readable. I think a better option would be to use only TryCast and format your original code slightly differently (although this isn't completely standard indentation, it is correct and F# compiler handles it fine):

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

This is probably the most readable option based on pattern matching, but you could also use if instad of the first match as in the version by kvb and combine it with TryCast (this really depends on personal preferences):

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

In any case, I believe that TryCast makes the code more readable as you avoid one nesting (which is othervise required because of :? .. as ..).

痞味浪人 2024-09-06 11:19:47

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

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

In case you mean "most effective way" as shortest code, i vote to AP too:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文