键入的ref cell?

pau*_*aul 1 union ocaml types ref dereference

我是一个ocaml菜鸟.使用普通的旧的ref或int或其他简单的内置类型,到目前为止对我来说都是各方面的预期.我在元组的上下文中使用它们,其中ref是元组的成员.我可以更新refs,取消引用它们等.

 # let e = 1, ref 1;;
 val e : int * int ref = (1, {contents = 1})
 # snd e := 2;;
 - : unit = ()
 # e;;
 - : int * int ref = (1, {contents = 2})
 # !(snd e);;
 - : int = 2
Run Code Online (Sandbox Code Playgroud)

但是一旦我将一个命名类型"ref"声明为其他聚合(甚至是内置的简单)类型,事情总的来说就很糟糕了.我发现我不能再改变引用了,因为之前它们没有被声明为某种类型的"ref"类型.的!并且:=运算符失败.

语义似乎以奇怪的不一致的方式改变.以下只是一个例子.为什么编写下面的第一个代码块是合法的,但在顶部循环中执行类似的操作似乎是非法的(下面进一步说明)?第一个块被编译器接受,并且我们可以匹配一个构造类型的ref并使用!来访问它的值.第13行和第14行的运算符.这都在循环队列的上下文中,并使用#use从顶部循环中的文件加载:

type 'a element = 'a * 'a pointer 
and 'a pointer = Pointer of 'a element ref;;
let next (_,n) = n;;
type 'a queue = 'a element option ref;;

let create () = None;;
(*passes compiler and behaves well*)
let enqueue queue x = 
  match !queue with
      None ->
    let rec elem = (x, Pointer (ref elem)) in 
    queue := Some elem;
    | Some (_, Pointer last_newest_next) -> (*Insert between newest and oldest*)
      let oldest = !last_newest_next in
      let elem = (x, Pointer (ref oldest)) in
      last_newest_next := elem;
      queue := Some elem;;
Run Code Online (Sandbox Code Playgroud)

在顶部循环中,类似的努力(以及对此的变化)失败,如下所示,我还使用函数分解元组,然后尝试调用相同的运算符:

let rec elem = (1, Pointer (ref elem));;
let last = !(next elem);;
Characters 12-22:
let last = !(next elem);;
          ^^^^^^^^^^
Error: This expression has type int pointer
   but an expression was expected of type 'a ref
Run Code Online (Sandbox Code Playgroud)

是的,我正在使用-rectypes,但我想尝试一次,而不使用递归缩写类型,我从那时起就一直坚持下去.请注意以下工作在顶部循环,但我不确定它是等效的,我真正想要的:

let last = next elem;;
val last : int pointer = (* ...  *)
Run Code Online (Sandbox Code Playgroud)

如果在第14行更改了第一个代码块而不使用!操作员,它打破了.重写(如下所示)会导致enqueue函数通过编译器但行为异常:

 (*compiles but fails - que only ever holds one item*)
let enqueue queue x = 
  match !queue with
      None ->
    let rec elem = (x, Pointer (ref elem)) in 
    queue := Some elem;
    | Some (_, Pointer last_newest_next) ->
      let oldest = last_newest_next in
      let elem = (x, Pointer oldest) in
      last_newest_next := elem;
      queue := Some elem;;
Run Code Online (Sandbox Code Playgroud)

一定是没有了!操作符(以及一些其他更改),倒数第二行实际上是在elem指向自身的指针,而不是更新不同的指针(从匹配的分解元素内)指向最初预期的elem.无论如何,我仍然不明白为什么语义似乎在类型化的ref的顶部循环元组分解和从ml文件中做同样的事情之间是不一致的......如果这甚至是所有这一切的原因.或者从模式匹配分解是否与通过函数分解元组不一样?!?

我使用了一个dequeue函数来测试上述函数的行为:

let dequeue queue = 
  match !queue with
      None -> raise Not_found
    | Some (_, Pointer oldest_ref) ->
      let oldest = !oldest_ref in
      let (x, Pointer next_ref) = oldest in
      let next = !next_ref in
      if next == oldest then
    queue := None
      else 
    oldest_ref := next;
      x;;
Run Code Online (Sandbox Code Playgroud)

我可以理解为什么我可能想用功能语言避开ref单元格,但我需要知道如何在必要时使用它们(没有双关语意).

Jef*_*eld 5

在你写的东西中,我很难找到一个特定的问题.但是,OCaml并不矛盾或不合逻辑.这是"现代"FP语言的美丽之一 - 它们的类型系统基于声音数学.现在我将专注于您展示的第一件不起作用的东西:

# let rec elem = (1, Pointer (ref elem));;
# let last = !(next elem);; ## TYPE ERROR HERE
Run Code Online (Sandbox Code Playgroud)

如果你只是看看是什么,似乎问题很明显next elem.从你的定义中可以看出elem,next elemPointer (ref elem).这不是参考.它的构造函数是Pointer.所以将!运算符应用于它是没有意义的,这就是类型错误告诉你的.如果你想elem退出next elem,你需要解构Pointer构造函数.

# let unpointer (Pointer x) = x;;
# let last = !(unpointer (next elem));;
# last == elem;;
- : bool = true
# 
Run Code Online (Sandbox Code Playgroud)

编辑:对于它的价值,你的循环列表类型对我来说有点复杂.如果您确实需要循环列表,可以查看OCaml Batteries Included:BatDllist中出现的双向链表实现.这是一个简单的低级实现,看起来很像你在C中写的.更好的是使用内置的OCaml列表!在多年的OCaml编码(仅一个数据点)中,我从未觉得需要使用循环列表.