【问题标题】:Why use pattern matching in OCaml为什么在 OCaml 中使用模式匹配
【发布时间】:2016-01-14 20:08:18
【问题描述】:

让我们考虑这个小函数

let f x =
     match x with
         0 -> 1 |
         _ -> x ;;

这在逻辑上等价于

let f x = 
   if x = 0 then 1 else x ;;

如果我们可以使用 if/else 达到相同的目的,那么模式匹配的目的是什么?

【问题讨论】:

    标签: functional-programming pattern-matching ocaml


    【解决方案1】:

    在您的精确示例中,模式匹配不会带来很多,因为您只有两种情况,更重要的是因为您的模式没有任何变量。只要用if/then/else写这个例子,你就明白了:

    let rec map f = function
        [] -> []
      | a::l -> let r = f a in r :: map f l
    

    另请注意,如果您有多余的案例或忘记了某些案例,模式匹配会向您发出警告。

    【讨论】:

      【解决方案2】:

      通常模式匹配允许编译器应用更积极的优化技术。在 if/then/else 表达式中,条件是任意表达式,可能包含副作用。例如,相等运算符可以做任何事情,因此编译器通常不能相信x=0 意味着x 等于0。模式匹配中的子句始终是常量,匹配意味着语法相等,不能重载,所以可以很方便的直接编译成汇编比较操作。在if 的示例中,比较通常会编译为函数调用(但在这种情况下,afaik 编译器足够聪明,生成的代码将是相同的)。

      但是 if/then/else 和模式匹配的主要区别在于后者是并行运行并编译成嵌入到程序集中的二叉搜索树,而 if/then/else 只是一个线性比较序列(参见this 了解更多信息)。

      更新

      为了满足 OP 的好奇心,我添加了一些汇编输出。不需要懂x86汇编,只要比较几条指令就可以了。你会看到的。

      正如我预测的那样,编译器确实发出了几乎相同的代码,对你来说具有相同的性能:

      函数with_match 已编译为高效代码(注意在OCaml 中01

      with_match:
      .L101:
          cmpq    $1, %rax
          je  .L100
          ret
      .L100:
          movq    $3, %rax
          ret
      

      对于函数with_if,编译器也发出了最优代码。唯一的区别是with_if函数中,跳转指令的条件是反转的。

      with_if:
      .L103:
          cmpq    $1, %rax
          jne .L102
          movq    $3, %rax
          ret
      .L102:
          ret
      

      这是可能的,因为编译器使用了一个技巧,允许他处理 = 作为一个特殊函数,附有一些理论。但一般来说这是不可能的,因为= 可以是任意函数。我们可以很容易地混淆编译器,通过在文件开头添加以下行:

      let (=) x y = x = y 
      

      现在所有的技巧都被禁用了,编译器会发出这个低效的代码。

      with_if:
          subq    $8, %rsp
      .L105:
          movq    %rax, 0(%rsp)
          movq    $1, %rsi
          movq    %rax, %rdi
          movq    _caml_equal, %rax
          call    _caml_c_call
      .L106:
          movq    _caml_young_ptr, %r11
          movq    (%r11), %r15
          cmpq    $1, %rax
          je  .L104
          movq    $3, %rax
          addq    $8, %rsp
          ret
      .L104:
          movq    0(%rsp), %rax
          addq    $8, %rsp
          ret
      

      话虽如此,我想强调的是,人们不应该更喜欢 match 而不是 if 或反之亦然。应该选择更干净且代码更易读的结构。而且 ocaml 编译器相当不错,并且会为您生成高效的代码。

      我个人更倾向于比赛,因为这反映了我的思维方式。我很难根据 if/then/else 结构进行推理,每当我阅读它们时,我都会在脑海中将它们翻译成与子句匹配的内容。但这是我个人的问题。随意使用更适合您的任何构造。

      【讨论】:

      • 但是请注意,守卫可能会产生副作用。
      • @antron:什么是警卫?它们与 C++ 中的相同吗? (#ifdef MYHEADER_H ... #endif)
      • 我想通过实验证明这一点。 :) 编译这个(以及其他变体的类比文件):paste.ubuntu.com/12817123 并运行 time ./exec 是一个可靠的基准吗?
      • 不,不会。更好的方法是查看汇编并比较一些指令。但我敢打赌,对于您的特定示例,编译器会很聪明,足以生成相同的代码,因为它使用了一些启发式方法。
      • @marmistrz:是这个:caml.inria.fr/pub/docs/manual-ocaml/expr.html#sec122。守卫不是模式的一部分,但是,如您所见,您可以使用它们在match 案例上设置任意条件,包括评估导致副作用的条件。
      【解决方案3】:

      检测到部分模式匹配:

      type number = Zero | One | Two;
      
      let f= function
          Zero -> 0
        | One  -> 1 ;;
      
      Warning 8: this pattern-matching is not exhaustive.
      Here is an example of a value that is not matched:
      Two
      val f : number -> int = <fun>
      

      【讨论】:

        猜你喜欢
        • 2016-04-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-11
        • 1970-01-01
        • 1970-01-01
        • 2011-03-03
        • 1970-01-01
        相关资源
        最近更新 更多