键入的ref cell?

时间:2012-12-27 06:28:35

标签: types ocaml union dereference ref

我是一个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

但是一旦我将一个命名类型“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;;

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

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

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

let last = next elem;;
val last : int pointer = (* ...  *)

如果在第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;;

一定是没有了!操作符(以及一些其他更改),倒数第二行实际上是在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;;

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

2 个答案:

答案 0 :(得分:5)

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

# let rec elem = (1, Pointer (ref elem));;
# let last = !(next elem);; ## TYPE ERROR HERE

如果您只看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
# 

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

答案 1 :(得分:4)

您似乎对编程语言的语义有着极低级别的理解。在这种情况下坚持认为低,似乎导致你误入歧途。 OCaml中引用的语义是一致的,你在这里观察到的一切都是由于你的错误,而不是语言语义。

如果这可以帮到你,这里有一种方法可以向那些在低实现级别下思考的人描述OCaml的语义。这不是我通常会向初学者描述它的方式,但如果你坚持用指针,聚合和指针思考:

    OCaml中的
  • 值可以用整数和不可变来表示,并按原样传递,或者指向某个堆块的指针;在这种情况下,可变性更容易理解,在一种语言中,通过价值/参考区别,除非明确地解构或重建,否则一切都是共享的

  • 引用是定义为具有可变字段type 'a ref = {mutable contents: 'a};的记录的派生概念,这对应于一个指向值的简单框,可以更改它指向的值;它是多态的,并且以一致的方式表现。特别是,您观察到的任何输入错误都是由于代码中的错误造成的,看起来更难!

  • type foo = Foo of t类型与t不同;您可以使用v : tfoo转换为Foo v,将v : foo转换为t (match v with (Foo x) -> x)

我认为你在同时混合太多困难。删除-rectypes并使代码正常工作,然后您可以考虑将其添加回来以查看它是否可以减轻代码。默认情况下未启用-rectypes的原因是某些代码肯定是错误(let sum x = x (* + *) x)被接受,只是在以后使用它时会产生一个神秘的错误消息。如果您对该语言的其他方面感到不舒服,那么您不希望这种情况发生。