混合DU和x27时的F#模式匹配;s和其他值
表达以下代码的最有效方式是什么混合DU和x27时的F#模式匹配;s和其他值,f#,pattern-matching,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
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的特殊值,则循环应该退出并返回break值
有没有办法将这段代码压缩成更小的内容?我认为您编写的代码很好。这里有一个我稍微喜欢的替代方案:
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
我不太喜欢匹配布尔值而不是使用
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#sas
操作符
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
让我们尝试播放一些x
|无
...
如果是cond.EvalBool(),则
将body.Eval()|>tryCast与
|Some(Break u作为e)->box e//Break是ControlFlowModifier的DU元素
|->next()//所有其他值都应调用next()
否则无效
我最终为此创建了一个活动模式。
其他地方也存在类似的逻辑,因此我可以使其可重用
let rec next() : obj =
if cond.EvalBool() then
match body.Eval() with
| IsBreak(res) -> res
| _ -> step.Eval() |> ignore ; next()
else null
看起来不错?我想指出的是,
Eval
的结果类型似乎有一个子类型层次结构,如果它也是一个DU,那么您可以执行以下操作
match body.Eval() with
| ControlFlowModifier(Break e) -> box e
| _ -> next()
嵌套模式万岁。如果你的意思是“最有效的方式”作为最短的代码,我也投票给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
要展平嵌套的
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
编辑:正如罗杰在评论中指出的那样,这个版本的代码可能不太可读。我认为更好的选择是只使用TryCast
,并稍微改变原始代码的格式(虽然这不是完全标准的缩进,但它是正确的,而且F#compiler处理得很好):
这可能是基于模式匹配的最具可读性的选项,但您也可以使用kvb版本中第一个匹配的instad,并将其与TryCast
结合使用(这实际上取决于个人偏好):
在任何情况下,我相信TryCast
会使代码更具可读性,因为您避免了一个嵌套(因为:?…as.
)而需要另一个嵌套)。@Kha-奇怪的是,我们的方法是多么相似。事实上,我也打算使用box e
而不是e:>obj
,但我认为这完全是一种个人风格的东西……是的,嵌套模式非常棒。当你开始引入活动模式时,情况就更糟了。嗯,把DU类型的名称放在前面,然后像这样把DU元素放在里面似乎不起作用?无法将其编译此假设body.Eval()返回类型为Foo的值,这是一个具有大小写“ControlFlowModifier of Bar”的DU,其中Bar也是一个具有大小写“Break of Scope”或其他任何内容的DU。虽然我确实觉得这种方法在技术上很有趣,但代码的可读性几乎消失了(IMO)。@罗杰:是的,我觉得原来的代码已经可以了。也许只使用<>代码> Tycase将是最好的选择。对你的问题不是真正的答案,但是,如果你的代码看起来像这样(使用<代码>:/<代码>和<代码> null < /代码>),那么你可以考虑一个更惯用的解决方案或包装器。
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
match cond.EvalBool() with
| false -> null
| true ->
match body.Eval() with
| TryCast(Break(scope) as e) -> e :> obj
| _ -> next()
if cond.EvalBool() then
match body.Eval() with
| TryCast(Break(scope) as e) -> e :> obj
| _ -> next()
else null