2016-02-27 60 views
2

(声明:我相当肯定,这是不以任何方式惯用如果OCaml中一些替代树遍历成语,我所有的耳朵:))。创建访问者模式与多态递归模块

我在OCaml中编写了一个玩具编译器,我想为我的大型语法树类型设置一个访问者。我写了一个使用类,但我认为尝试和使用模块/函子实现一个会很有趣。我的类型层次结构非常庞大,所以让我说明我正在尝试做什么。

考虑以下类型定义(做这些了当场):

type expr = 
    SNum of int 
    | SVarRef of string 
    | SAdd of expr * expr 
    | SDo of stmt list 

and stmt = 
    SIf of expr * expr * expr 
    | SAssign of string * expr 

让我简要地说明用法。比如说,我想收集程序中的所有SVarRef。如果我有一个映射访问者(其中一个访问树的每一个节点,并不会在默认情况下没有),我可以做以下(在一个完美的世界):

module VarCollector : (sig 
    include AST_VISITOR 
    val var_refs : (string list) ref 
end) = struct 
    include MapVisitor 

    let var_refs = ref [] 

    let s_var_ref (s : string) = 
    var_refs := s::!var_refs 
    SVarRef(s) 
end 

(* Where my_prog is a stmt type *) 
let refs = begin 
    let _ = VarCollector.visit_stmt my_prog in 
    VarCollector.!var_refs 
end 

我要指出的有功能的优势对于每个特定的变体,我的实际代码库都有一个类型,它们都有大量的变体,并且没有意义分解。特定于变体的函数允许避免对类型的其他变体重复执行迭代。

看起来很简单,但有以下几点:有不同类型的访问者。 MapVisitor返回原来的语法树,所以它的类型是

sig 
    (** Dispatches to variant implementations *) 
    val visit_stmt : stmt -> stmt 
    val visit_expr : expr -> expr 
    (** Variant implementations *) 
    val s_num : int -> expr 
    val s_var_ref : string -> expr 
    val s_add : (expr * expr) -> expr 
    val s_do : stmt list -> expr 
    val s_if : (expr * expr * expr) -> stmt 
    val s_assign : (string * expr) -> stmt 
end 

与此同时,人们可能想象一个折叠游客在返回类型是一些t为每个函数。试图摘要尽可能地,这里是我的尝试:

module type AST_DISPATCHER = sig 
    type expr_ret 
    type stmt_ret 
    val visit_expr : expr -> expr_ret 
    val visit_stmt : stmt -> stmt_ret 
end 
(** Concrete type designation goes in AST_VISITOR_IMPL *) 
module type AST_VISITOR_IMPL = sig 
    type expr_ret 
    type stmt_ret 

    val s_num : int -> expr_ret 
    val s_var_ref : string -> expr_ret 
    val s_add : (expr * expr) -> expr_ret 
    val s_do : stmt list -> expr_ret 

    val s_if : (expr * expr * expr) -> stmt_ret 
    val s_assign : (string * expr) -> stmt_ret 
end 
module type AST_VISITOR = sig 
    include AST_VISITOR_IMPL 
    include AST_DISPATCHER with type expr_ret := expr_ret 
          and type stmt_ret := stmt_ret 
end 

(** Dispatcher Implementation *) 
module AstDispatcherF(IM : AST_VISITOR_IMPL) : AST_DISPATCHER = struct 
    type expr_ret = IM.expr_ret 
    type stmt_ret = IM.stmt_ret 

    let visit_expr = function 
    | SNum(i) -> IM.s_num i 
    | SVarRef(s) -> IM.s_var_ref s 
    | SAdd(l,r) -> IM.s_add (l,r) 
    | SDo(sl) -> IM.s_do sl 

    let visit_stmt = function 
    | SIf(c,t,f) -> IM.s_if (c,t,f) 
    | SAssign(s,e) -> IM.s_assign (s,e) 
end 
module rec MapVisitor : AST_VISITOR = struct 
    type expr_ret = expr 
    type stmt_ret = stmt 
    module D : (AST_DISPATCHER with type expr_ret := expr_ret 
           and type stmt_ret := stmt_ret) 
    = AstDispatcherF(MapVisitor) 

    let visit_expr = D.visit_expr 
    let visit_stmt = D.visit_stmt 

    let s_num i = SNum i 
    let s_var_ref s = SVarRef s 
    let s_add (l,r) = SAdd(D.visit_expr l, D.visit_expr r) 
    let s_do sl = SDo(List.map D.visit_stmt sl) 

    let s_if (c,t,f) = SIf(D.visit_expr c, D.visit_expr t, D.visit_expr f) 
    let s_assign (s,e) = SAssign(s, D.visit_expr e) 
end 

运行这给了我下面的错误消息,但是:

Error: Signature Mismatch: 
     Values do not match: 
     val visit_expr : expr -> expr_ret 
     is not included in 
     val visit_expr : expr -> expr_ret 

我知道这意味着我没有表达的关系正确的类型之间,但我无法弄清楚这种情况下的修复程序。

回答

2

免责声明:模块只是伴随着类型定义的值的记录。由于模块中没有类型,因此根本不需要使用它们,只需使用普通的旧记录类型即可,并且您将获得其中一种惯用的AST遍历模式。很快,你会发现你需要一个开放的递归,并且会切换到基于类的方法。无论如何,这是为什么课程被添加到OCaml的主要原因。你也可以将你的类包装成状态monad,用任意用户数据折叠AST。

关于你的错误信息,那么它是什么简单的,你隐藏你的类型的签名,一个常见的错误。最简单的解决方案是省略仿函数的返回类型的注释在所有,或传播与with type expr = expr注释类型平等。

如果你需要更多的惯用方法的例子,然后记录你可以去ppx mappers,这里是类实现不同的观众,其中those被包装成一个状态单子的example