Functional programming 两次传递来标记语法树中的出现?

Functional programming 两次传递来标记语法树中的出现?,functional-programming,ocaml,Functional Programming,Ocaml,假设您有一个语法树,其中包含表示标识符的节点。比如: type node = ... | Ident of string ... 我要遍历树,并用该标识符的出现次数标记所有Ident节点。例如,如果标识符x在树中出现3次,则每个Ident x节点都将标记为3 显然,这需要修改decl类型以适应新的数据,比如字符串的Ident*ref int。我的问题是什么是实现这一点的最佳方法?似乎是一种两次通过的方法——一个进行计数,一个进行标记——可能是最简单的方法,但即使如此,跟踪给定标识符的节点似乎也

假设您有一个语法树,其中包含表示标识符的节点。比如:

type node = ...
| Ident of string
...
我要遍历树,并用该标识符的出现次数标记所有Ident节点。例如,如果标识符x在树中出现3次,则每个Ident x节点都将标记为3

显然,这需要修改decl类型以适应新的数据,比如字符串的Ident*ref int。我的问题是什么是实现这一点的最佳方法?似乎是一种两次通过的方法——一个进行计数,一个进行标记——可能是最简单的方法,但即使如此,跟踪给定标识符的节点似乎也会很尴尬


建议

这里是一个纯功能解决方案:

type 'a exp = Ident of 'a | Const of int | Add of 'a exp * 'a exp

(* pass 1: count occurrences of identifiers *)
let count : string exp -> (string * int) list =
  let rec record x = function
    | [] -> [(x, 1)]
    | (y, n) :: counts when x = y -> (y, n + 1) :: counts
    | count :: counts -> count :: record x counts
  in
  let rec visit acc = function
    | Ident x -> record x acc
    | Const _ -> acc
    | Add (e1, e2) -> visit (visit acc e1) e2
  in
  visit []

(* pass 2: mark all identifiers with their occurrence counts *)
let mark (counts : (string * int) list) : string exp -> (string * int) exp =
  let rec lookup x = function
    | [] -> invalid_arg "lookup"
    | (y, n) :: _ when x = y -> n
    | _ :: counts -> lookup x counts
  in
  let rec visit = function
    | Ident x -> Ident (x, lookup x counts)
    | Const n -> Const n
    | Add (e1, e2) -> Add (visit e1, visit e2)
  in
  visit

(* combining the two passes *)
let mark_with_counts e = mark (count e) e
请注意,两个通行证可合并为一个单一的就诊功能,有效避免重复模式匹配:

let mark_with_counts' e =
  let rec record x = function
    | [] -> [(x, 1)]
    | (y, n) :: counts when x = y -> (y, n + 1) :: counts
    | count :: counts -> count :: record x counts
  in
  let rec lookup x = function
    | [] -> invalid_arg "lookup"
    | (y, n) :: _ when x = y -> n
    | _ :: counts -> lookup x counts
  in
  let rec visit acc = function
    | Ident x -> (record x acc, fun counts -> Ident (x, lookup x counts))
    | Const n -> (acc, fun _ -> Const n)
    | Add (e1, e2) ->
        let (acc, syn1) = visit acc e1 in
        let (acc, syn2) = visit acc e2 in
        (acc, fun counts -> Add (syn1 counts, syn2 counts))
  in
  let (counts, syn) = visit [] e in
  syn counts
为什么还要修改所有标识符节点?这是过度冗余的,并没有比边上的string->int映射方便多少。