代码之家  ›  专栏  ›  技术社区  ›  zpavlinovic

OCaml访问者模式

  •  7
  • zpavlinovic  · 技术社区  · 10 年前

    我在OCaml中实现了一种简单的类C语言,与往常一样,AST是我的中间代码表示。由于我将在树上进行相当多的遍历,我想实现 缓解疼痛的访客模式。我的AST目前遵循语言的语义:

    type expr = Plus of string*expr*expr | Int of int | ...
    type command = While of boolexpr*block | Assign of ...
    type block = Commands of command list
    ...
    

    现在的问题是树中的节点是不同类型的。理想情况下,我会通过 访问处理节点的单个功能的过程;该过程将打开节点的类型并相应地执行工作。现在,我必须为每个节点类型传递一个函数,这似乎不是最佳解决方案。

    在我看来,我可以(1)真的采用这种方法,或者(2)上面只有一种类型。通常的做法是什么?也许使用OO?

    4 回复  |  直到 10 年前
        1
  •  11
  •   Andreas Rossberg    10 年前

    没有人在函数式语言中使用访问者模式——这是一件好事。有了模式匹配,幸运的是,只需使用(相互)递归函数,就可以更容易和直接地实现相同的逻辑。

    例如,假设您想为AST编写一个简单的解释器:

    let rec run_expr = function
      | Plus(_, e1, e2) -> run_expr e1 + run_expr e2
      | Int(i) -> i
      | ...
    
    and run_command = function
      | While(e, b) as c -> if run_expr e <> 0 then (run_block b; run_command c)
      | Assign ...
    
    and run_block = function
      | Commands(cs) = List.iter run_command cs
    

    访问者模式通常只会使这一点复杂化,尤其是当结果类型是异类时,如这里所示。

        2
  •  6
  •   Leo White    10 年前

    如果您必须在一组相互递归的数据类型(例如AST)上编写许多不同的递归操作,那么您可以使用开放递归(以类的形式)来编码遍历,并为自己节省一些麻烦。

    Real World OCaml .

        3
  •  5
  •   Virgile    10 年前

    确实可以为每种类型的AST定义一个访问方法(默认情况下不做任何事情),并让您的访问函数将该类的一个实例作为参数。事实上,这种机制在OCaml世界中被使用,尽管并不经常使用。

    特别是 CIL library 有访客类 (参见 https://github.com/kerneis/cil/blob/develop/src/cil.mli#L1816 用于接口)。请注意,CIL的访问者本质上是必须的(转换已到位)。然而,定义将AST映射到另一个AST的访问者是完全可能的,例如 Frama-C ,基于CIL,提供现场和复制访问者。最后 Cαml ,是一个AST生成器,用于轻松处理绑定变量、生成映射并将访问者与数据类型一起折叠。

        4
  •  0
  •   Christophe Dony    2 年前

    访问者模式(以及与可重用软件相关的所有模式)与包含多态上下文(子类型和继承)中的可重用性有关。 Composite解释了一种解决方案,在该解决方案中,您可以向现有子类型添加新的子类型,而无需修改后者的代码。 Visitor解释了一个解决方案,在该方案中,您可以向现有类型(及其所有子类型)添加新函数,而无需修改类型代码。 这些解决方案属于面向对象编程,需要使用动态绑定发送消息(方法调用)。

    您可以在Ocaml中做到这一点,因为您使用了“O”(对象层),但由于具有强类型的优点,会有一些限制。

    在OCaml中,有一组相关类型,决定是使用类层次结构和消息发送,还是如andreas所建议的那样,使用具体的(代数)类型以及模式匹配和简单的函数调用,是一个难题。

    混凝土类型不等同。如果选择后者,则在定义和编译节点类型之后,将无法在AST中定义新节点。一旦说a是A1或A2,你就不能在不修改源代码的情况下在后面说还有一些A3。

    在您的案例中,如果您想要实现访问者,请用类及其子类替换EXPR具体类型,用方法替换函数(顺便说一下,这些方法也是函数)。然后,动态绑定将完成此任务。