表达以下代码的最有效方法是什么?
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的特殊值,则循环应退出并返回中断值。
有没有办法将该代码块压缩为更小的代码?
答案 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