【问题标题】:Suggestion for solving fragile pattern matching解决脆弱模式匹配的建议
【发布时间】:2011-05-19 19:33:23
【问题描述】:

我经常需要匹配应该具有相同构造函数的值元组。包罗万象的_,_ 总是在最后结束。这当然是脆弱的,任何添加到该类型的额外构造函数都将编译得非常好。我目前的想法是让匹配项连接第一个但不是第二个参数。但是,还有其他选择吗?

例如,

type data = | States of int array 
            | Chars  of (char list) array

let median a b = match a,b with
    | States xs, States ys ->
        assert( (Array.length xs) = (Array.length ys) );
        States (Array.init (Array.length xs) (fun i -> xs.(i) lor ys.(i)))
    | Chars xs, Chars ys -> 
        assert( (Array.length xs) = (Array.length ys) );
        let union c1 c2 = (List.filter (fun x -> not (List.mem x c2)) c1) @ c2 in
        Chars (Array.init (Array.length xs) (fun i -> union xs.(i) ys.(i)))
    (* inconsistent pairs of matching *)
    | Chars  _, _
    | States _, _ -> assert false

【问题讨论】:

    标签: functional-programming pattern-matching ocaml


    【解决方案1】:

    您可以使用下面略短的模式:

    | (Chars _| States _), _ -> assert false
    

    其实你可以让编译器为你生成,因为还是有点繁琐。 输入以下内容并编译:

    let median a b = match a,b with
    | States xs, States ys ->
        assert( (Array.length xs) = (Array.length ys) );
        States (Array.init (Array.length xs) (fun i -> xs.(i) lor ys.(i)))
    | Chars xs, Chars ys -> 
        assert( (Array.length xs) = (Array.length ys) );
        let union c1 c2 = (List.filter (fun x -> not (List.mem x c2)) c1) @ c2 in
        Chars (Array.init (Array.length xs) (fun i -> union xs.(i) ys.(i)))
    

    警告 8:这种模式匹配是 并不详尽。这是一个例子 一个不匹配的值:(Chars _, 州_)

    您现在可以将建议的模式复制粘贴回您的代码中。这通常是我如何为具有数十个构造函数的类型生成非脆弱的包罗万象的模式。您可能需要多次启动编译器,但仍然比自己键入要快。

    【讨论】:

    • 有趣!这个策略可能应该内置到 Haskell IDE 中?
    • 好吧,我宁愿没有所有这些编译警告——另外一个会丢失在将产生的剪切数中。我实际上不知道您可以在这样的元组中进行or 匹配。这应该可以节省一些行,谢谢。
    • @nlucaroni 如果我没有说清楚,我只是建议暂时收到警告,因为它包含一个包罗万象的非脆弱模式,可以复制粘贴回代码中。跨度>
    • @Jörg 如果有人决定这样做,相同技巧的另一个实例是在模块的签名中写入val f:unit,其中 f 实际上是一个具有复杂类型的函数对自己写作没有信心。错误消息包含模块中实现的f 类型。不过,不确定它是如何转化为 Haskell 的。
    • @Pascal:我不知道 Ocaml,但我认为这意味着将 f :: () 作为某些 f = ... 的类型签名(()unit 的 Haskell 拼写)您希望在哪里看到错误。或者,如果您使用 GHC,则只需放弃类型签名并在 GHCi 中运行 :t f
    【解决方案2】:

    这只是品味/风格的问题,但我倾向于将同一个构造函数上的子句组合在一起,而不是先将所有有用的子句放在一起,然后将所有“荒谬的情况”放在一起。当您为一个给定的构造函数编写多个“有用的”子句并想要检查您没有忘记任何内容时,这会非常有用。

    let median a b = match a,b with
      | States xs, States ys ->
        assert( (Array.length xs) = (Array.length ys) );
        States (Array.init (Array.length xs) (fun i -> xs.(i) lor ys.(i)))
      | States _, _ -> assert false
    
      | Chars xs, Chars ys -> 
        assert( (Array.length xs) = (Array.length ys) );
        let union c1 c2 = (List.filter (fun x -> not (List.mem x c2)) c1) @ c2 in
        Chars (Array.init (Array.length xs) (fun i -> union xs.(i) ys.(i)))
      | Chars _, _ -> assert false
    

    【讨论】:

      【解决方案3】:

      这是相当骇人听闻的(并导致警告),但您可以使用Obj 来检查标签是否相等。它应该捕获 a 和 b 具有不同值的所有情况:

      type data = | States of int array 
                  | Chars  of (char list) array
      
      let median a b = match a,b with
          | States xs, States ys ->
              assert( (Array.length xs) = (Array.length ys) );
              States (Array.init (Array.length xs) (fun i -> xs.(i) lor ys.(i)))
          | Chars xs, Chars ys -> 
              assert( (Array.length xs) = (Array.length ys) );
              let union c1 c2 = (List.filter (fun x -> not (List.mem x c2)) c1) @ c2 in
              Chars (Array.init (Array.length xs) (fun i -> union xs.(i) ys.(i)))
          (* inconsistent pairs of matching *)
          | x, y when (Obj.tag (Obj.repr x)) <> (Obj.tag (Obj.repr y)) -> assert false
      

      警告是针对非详尽的模式匹配(因为它无法判断受保护的子句是否与其余部分匹配)。

      编辑:你根本不需要使用 Obj,你可以直接比较 x 和 y:

      | x, y when x <> y -> assert false
      

      不幸的是,这仍然会导致警告。

      【讨论】:

      • 在我看来,这些警告的重点不是确保执行顺利,而是如果您更改数据类型,您将收到警告,重新考虑此模式并可能实现适应变化的新行为。您的技术在动态上很聪明,但没有给出那些非常有助于可维护性的有用警告(或将它们变成噪音)。
      • 我的解决方案比这更糟糕!它没有考虑常量构造函数。我将为此对其进行编辑,但是,是的,失去警告的价值几乎肯定是不值得的。
      猜你喜欢
      • 2017-07-30
      • 1970-01-01
      • 2016-04-01
      • 2018-01-11
      • 2015-08-29
      • 2016-04-29
      • 1970-01-01
      • 2017-11-03
      • 2019-04-30
      相关资源
      最近更新 更多