2010-04-23 47 views
2

表达以下代码的最有效方法是什么?混合DU和其他值时的F#模式匹配

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返回boolean的结果,其中假应该返回null 和真应该要么再次运行整个块(其裹在一个叫做未来FUNC) 或者休息的特殊价值被发现,然后该循环应该退出并返回中断值。

有什么办法可以将代码块压缩到更小的东西吗?

+0

不是你的问题的答案,但是,如果你的代码看起来像这样(使用':?'和'null'),那么你可能会考虑一个更习惯的F#解决方案或包装器。 – 2012-12-16 21:18:48

回答

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 
+0

嗯,你打我:) – 2010-04-23 14:54:21

+0

@Kha - 它的奇怪之处在于我们的方法有多相似。实际上,我也打算使用'box e'而不是'e:> obj',但是我们认为它完全是一种个人风格的东西... – kvb 2010-04-23 15:01:51

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 
1

我最终为此创建了一个活跃的模式。 同样的逻辑也存在于其他地方,所以我可以使其可重复使用

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

看起来体面?

3

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

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

华友世纪嵌套模式。

+0

是的,嵌套模式很棒。当你开始引入活跃模式时更是如此。 – gradbot 2010-04-23 16:55:12

+0

嗯,它似乎没有工作把DU类型的名称和DU元素里面呢? 无法让它编译 – 2010-04-23 19:50:19

+0

这假定body.Eval()返回一个类型为Foo的值,它是一个DU,它有一个“ControlFlowModifier of Bar”的情况,其中Bar也是一个DU,它有一个“Break of Scope”管他呢。 – Brian 2010-04-23 21:57:59

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

编辑:罗杰在评论中指出,这个版本的代码可能不是很可读。我认为一个更好的选择是只使用TryCast和略有不同格式化你的原代码(虽然这不完全是标准的缩进,这是正确的,F#编译器处理它精细):

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

这可能是基于最可读的选项模式匹配,但你也可以通过KVB使用if instad第一match作为版本,并与TryCast结合起来(这完全取决于个人喜好):

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

在任何情况下,我相信TryCast使代码更具可读性,因为您避免了一个嵌套(由于:? .. as ..而需要其他嵌套)。

+0

尽管我发现这种方法技术上很有趣,但代码的可读性几乎消失了(IMO)。 – 2010-04-24 15:03:28

+0

@Roger:是的,我确实认为原来的代码已经可以。也许只使用'TryCast'将是最好的选择。 – 2010-04-24 15:28:30