Graph 在OCaml中定义具有循环的图形节点变量

Graph 在OCaml中定义具有循环的图形节点变量,graph,ocaml,self-reference,nfa,cyclic,Graph,Ocaml,Self Reference,Nfa,Cyclic,我正在尝试实现一个regex到NFA的转换器。我已经编写了大部分代码,但我正在努力找到一种方法,在给定状态(节点)和边表示的情况下,用一个循环来构建一个图 我的图形表示如下: type state = | State of int * edge list (* Node ID and outgoing edges *) | Match (* Match state for the NFA: no outgoing edges *) and edge = | Edge of state * str

我正在尝试实现一个regex到NFA的转换器。我已经编写了大部分代码,但我正在努力找到一种方法,在给定状态(节点)和边表示的情况下,用一个循环来构建一个图

我的图形表示如下:

type state =
| State of int * edge list (* Node ID and outgoing edges *)
| Match (* Match state for the NFA: no outgoing edges *)
and edge =
| Edge of state * string (* End state and label *)
| Epsilon of state (* End state *)
我将正则表达式转换为NFA的函数基本上是正则表达式类型的模式匹配,接受正则表达式类型和“最终状态”(NFA的所有输出边都将进入该状态),并返回该正则表达式的(部分构建的)NFA的“开始状态”。NFA片段是通过返回由其传出边缘列表构造的状态来构建的,其中每个边缘的结束状态是通过递归调用构造的

大多数代码都很简单,但是我在为Kleene star和+(需要在图中循环)构建NFA时遇到了麻烦。根据我的陈述,我最终得出如下结论:

let rec regex2nfa regex final_state =
  match regex with
  ... (* Other cases... *)
  | KleeneStar(re) ->
      let s = State(count, [Epsilon(regex2nfa r s); Epsilon(final_state)]) in
      s
显然,这不会编译,因为此时s是未定义的。但是,我也不能添加“rec”关键字,因为类型检查器将(正确地)拒绝这样一个递归定义的类型,我不能通过使用Lazy来解决这个问题,因为强制计算“s”将递归地强制它(一次又一次…)。基本上,我这里有一个鸡和蛋的问题-我需要在完全构造“state”引用之前将其传递给另一个将有优势返回到它的状态,但是当然原始状态必须完全构造才能在递归调用中传递


在不使用引用/可变记录的情况下,有没有其他方法可以做到这一点?我真的很想让它尽可能地发挥作用,但鉴于目前的情况,我看不到解决这个问题的办法。。。有人有什么建议吗?

您可以使用惰性类型或函数创建具有循环的数据结构,而无需显式引用。事实上,它们都隐藏着某种形式的可变性

下面是一个最简单的惰性结构示例,它比列表更复杂

type 'a tree = 'a tr Lazy.t
and  'a tr = Stem of 'a * 'a tree * 'a tree

let rec tree_with_loop : int tree =
  lazy (Stem (42,tree_with_loop,tree_with_loop))
但是,你应该明白,有了这种结构(即,那些包含循环的结构),你正在走向一个无限的脆弱地带,因为你所有的遍历函数现在都发散了

这是同样的例子,但没有懒惰:

type 'a tree = unit -> 'a tr
and  'a tr = Stem of 'a * 'a tree * 'a tree

let rec tree_with_loop : int tree =
  fun () -> Stem (42,tree_with_loop,tree_with_loop)
下面是一个稍微不那么无限的树的例子:

type 'a tree = 'a tr Lazy.t
and  'a tr =
  | Node of 'a
  | Tree of 'a tree * 'a tree

let rec tree_with_loop : int tree =
  lazy (Tree (tree_with_loop,
              lazy (Node 42)))

值得一提的是,我发现在OCaml中处理不可变的循环结构是不可行的(由于它的迫切性)。我已经完成了图形处理,其中每个节点都包含其后续节点的节点ID,而不是节点本身。然后我有一个单独的节点ID到节点的映射。映射查找有额外的开销,但对我来说是有效的。编译器不会拒绝递归值定义。具体来说,让我们尝试使用惰性值。为了引用
s
变量,需要将其放入惰性表单中。这意味着您的Epsilon构造函数应该接受
state Lazy.t
。还有其他的构造器。Erwig定义的归纳图怎么样?这绝对是我想要的,非常感谢!我以前尝试过使用Lazy,但并不真正理解它是如何工作的;这一解释(以及你的其他评论)使它更加清晰。它对Kleene star非常有效,尽管对于+我不得不使用引用,因为您必须返回完全计算的表达式。。。但我仍然能够成功地使这两个方面都发挥作用,而且我的程序基本上仍能正常运行:)