(OCaml)queue.ml 中使用的奇怪语法——`<-` 运算符

发布于 2024-10-13 05:06:29 字数 2308 浏览 5 评论 0 原文

在浏览 Caml Light 库的编程示例时,我偶然发现了以下代码(取自 Caml Light queue.ml 文件):

type 'a queue_cell =
    Nil
  | Cons of 'a * 'a queue_cell ref
;;

type 'a t =
  { mutable head: 'a queue_cell;
    mutable tail: 'a queue_cell }
;;

let add x = function
    { head = h; tail = Nil as t } ->    (* if tail = Nil then head = Nil *)
      let c = Cons(x, ref Nil) in
        h <- c; t <- c
  | { tail = Cons(_, ref newtail) as oldtail } ->
      let c = Cons(x, ref Nil) in
        newtail <- c; oldtail <- c
;;

FIFO 数据结构的这种实现让我感到困惑。我的总体想法是,保留指向结构中最后一个条目的指针,以便可以在末尾附加。这对我来说非常有意义。然而,让我烦恼的是如何完成此操作的语法。

请考虑以下事项:

  | { tail = Cons(_, ref newtail) as oldtail } ->
      let c = Cons(x, ref Nil) in
        newtail <- c; oldtail <- c

我在这里遇到类型问题。根据类型定义,newtail 的类型应为“队列单元”,因为它是在模式中使用 Cons(_, ref newtail) 检索的匹配:如果我理解正确的话,这意味着 newtail 绑定了 tail 记录字段的第二个成员(最初是一个参考)。

那么 newtail <- c 是什么意思?如果我尝试用 (fun x -> x <- c) newtail 替换此语句,我会得到 Theidentifier x is not mutable.,而代码听起来对我来说与原始版本完全相似。

将这几行重写为如下内容是否意味着相同?

  | { tail = Cons(_, newtail) as oldtail } ->
      let c = Cons(x, ref Nil) in
        newtail := c; oldtail <- c

进一步思考这个问题,下面的代码实际上做了什么?

type t = Nil | Node of (t ref);;
type box = {mutable field: t};;

let poke = function
  | {field = Node(ref n)} -> n <- Nil
  | {field = Nil} -> ()
;;

let test = {field = Node(ref (Node(ref Nil)))};;
poke test;;
test;;

写法一样吗

{field = Node(n)} -> n := Nil

{field = Node(ref n)} -> n <- Nil

更奇怪的是:以下代码返回 值标识符 a 未绑定。

let a = Nil;;
a <- Nil;; (* The value identifier a is unbound. *)

有人可以花时间为我澄清 <- 的使用吗?这里的各种例子让我很困惑......
谢谢!

编辑:这最初发布到 Caml 邮件列表,但我认为该帖子没有成功,所以我将其发布到这里。看来这个帖子确实起作用了;对此感到抱歉:邮件列表答案的链接(其原始作者也发布在这里)是 https://sympa-roc.inria.fr/wws/arc/caml-list/2011-01/msg00190.html

While browsing the Caml Light library for programming examples, I stumbled across the following code, taken from the Caml Light queue.ml file:

type 'a queue_cell =
    Nil
  | Cons of 'a * 'a queue_cell ref
;;

type 'a t =
  { mutable head: 'a queue_cell;
    mutable tail: 'a queue_cell }
;;

let add x = function
    { head = h; tail = Nil as t } ->    (* if tail = Nil then head = Nil *)
      let c = Cons(x, ref Nil) in
        h <- c; t <- c
  | { tail = Cons(_, ref newtail) as oldtail } ->
      let c = Cons(x, ref Nil) in
        newtail <- c; oldtail <- c
;;

This implementation of FIFO data structures puzzles me. I get the general idea, to keep a pointer to the last entry in the structure, so that appending at the end is possible. This makes perfect sense to me. However, it's the syntax of how this is done that bugs me.

Consider the following:

  | { tail = Cons(_, ref newtail) as oldtail } ->
      let c = Cons(x, ref Nil) in
        newtail <- c; oldtail <- c

I have a problem with types here. By the type definition, newtail should be of type 'a queue cell, since it's retrieved using Cons(_, ref newtail) in the pattern matching: if I understand correctly, this would mean that newtail binds the value pointed by the second member of the tail record field (which originally is a reference).

So what does the newtail <- c means? If I try to replace this statement by (fun x -> x <- c) newtail, I get The identifier x is not mutable., whereas the code sounds perfectly similar to the original variant to me.

Would rewriting these few lines to read as follows mean the same?

  | { tail = Cons(_, newtail) as oldtail } ->
      let c = Cons(x, ref Nil) in
        newtail := c; oldtail <- c

Taking the question one step further, what does the following code actually do?

type t = Nil | Node of (t ref);;
type box = {mutable field: t};;

let poke = function
  | {field = Node(ref n)} -> n <- Nil
  | {field = Nil} -> ()
;;

let test = {field = Node(ref (Node(ref Nil)))};;
poke test;;
test;;

Is it the same to write

{field = Node(n)} -> n := Nil

and

{field = Node(ref n)} -> n <- Nil

?

Even stranger: the following code returns The value identifier a is unbound.

let a = Nil;;
a <- Nil;; (* The value identifier a is unbound. *)

Could someone take the time to clarify the use of <- for me? The various examples here are pretty puzzling to me...
Thanks!

EDIT: This was originally posted to the Caml Mailing list, but I thought the post didn't make it, so I posted it here. It appears that the posting did work; sorry for that: the link to the mailing list answer (which its original author also posted here), is https://sympa-roc.inria.fr/wws/arc/caml-list/2011-01/msg00190.html.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

十年九夏 2024-10-20 05:06:29

请参阅我在 caml 列表 上的回答

为什么同一个问题要在不同的地方问两次?这只会导致重复工作,知识渊博的人会浪费时间来回答你。
如果您想这样做,请至少发布交叉引用(从您的 stackoverflow 帖子到列表存档,反之亦然[1]),以便人们可以检查该问题是否在其他地方尚未得到解答。

[1] 是的,您可以进行循环交叉引用,因为 stackoverflow 帖子是可变的!

可变字段和引用的语义发生了很大变化(永远)
介于 Caml Light 和 Objective Caml 之间。注意这个代码是Caml Light
具体——如果你想学习 Caml,你应该使用
Objective Caml,这是仍在维护的实现。
在 Objective Caml 中,只有记录字段是可变的。参考文献是派生的
概念,类型 'a ref 定义为:

type 'a ref = { 可变内容 : 'a } 

您可以使用语法 foo.bar <- baz 更改可变字段(其中“bar”是
记录字段,foo 和 baz 是任意表达式,foo 属于记录
类型)

在 Caml Light 中,记录字段是可变的,但求和类型字段(变体)是可变的
也是可变的;然而,这里不使用可变变量字段。看
http://caml.inria.fr/pub/docs /manual-caml-light/node4.6.html 用于
文档。

在 Caml Light 中,记录可能返回可变位置,类似于中的左值
类C语言。例如,使用可变变体

type foo = 可变 int 的 Foo 

你可以写:

let set_foo (f : foo) (n : int) = 
  将 f 与 
  |富禄 -> 
     位置<-n 

“foo <- bar”在这里用于将值“bar”分配给绑定在中的左值“foo”
可变的模式。
在您的示例中,使用了两种可变模式:

<前> <代码> | { tail = Cons(_, ref newtail) as oldtail } ->;

  • oldtail 是一个可变模式,表示可变的“tail”字段
    记录
  • (ref newtail) 是一种特定的语法,一种引用的模式。它绑定了一个
    对应于引用位置的可变模式“newtail”
    换句话说,在 Caml Light 中,您可以这样编写“:=”运算符:

    让前缀:= rv =
    将 r 与
    |参考位置->
    loc <- v

希望有帮助。

编辑:

关于奇怪的错误消息:我认为在内部,Caml Light 在范围内维护一个“值标识符”列表,这些标识符来自与可变字段(记录或变体)匹配的模式。当他们看到 foo <- bar 表达式时,他们会在该环境中查找相应的位置。这样的环境对于表达式来说是局部的,它永远不会逃脱。特别是在顶层它是空的,并且错误告诉您范围内不存在“值标识符”(可变模式)。

还有一件事:值标识符和普通标识符的命名空间并不不同。当您匹配值标识符时,Caml Light 会向范围添加值标识符(可变)以及具有匹配右值的相应标识符。这可能会非常令人困惑,因为您可能会改变位置,但值不会改变:(

#match ref 1 with (ref x) -> (x <- 2; x);;
- : int = 1


#match ref 1 with (ref x) as a -> (x <- 2; !a);;
- : int = 2

值)标识符将遮盖任何旧标识符(值标识符或不)

#let x = 1 in let (ref x) = ref 2 in x;;
- : int = 2

(如果您不知道,让pattern = e2 中的 e1 相当于 将 e1 与模式匹配 -> e2 (类型系统除外))

由于标识符和值标识符的语法类相同,因此不可变标识符也会隐藏值标识符,从而产生不同的错误:

#let (ref x) = ref 2 in let x = 1 in x <- 3;;
Toplevel input:
>let (ref x) = ref 2 in let x = 1 in x <- 3;;
>                                    ^^^^^^
The identifier x is not mutable.

See my answer on the caml list

Why ask the same question twice in different places ? This only leads to a duplication of efforts, with knowledgeable people wasting their time to answer you.
If you want to do that, please at least post cross-references (from your stackoverflow post to the list archive, and vice versa[1]), so that people can check that it hasn't been answered yet in the other place.

[1] yes, you can have cyclic cross-references, as the stackoverflow post is mutable!

The semantics of mutable fields and references has changed a lot (for good)
between Caml Light and Objective Caml. Beware that this code is Caml Light
specific -- and if you want to learn Caml, you should rather be using
Objective Caml, which is the implementation that is still maintained.
In Objective Caml, only records fields are mutable. References are a derived
concept, the type 'a ref is defined as :

type 'a ref = { mutable contents : 'a } 

You change a mutable field with the syntax foo.bar <- baz (where "bar" is
a record field, and foo and baz are any expression, foo being of a record
type)

In Caml Light, record fields are mutable, but sum type fields (variants) are
mutable as well; mutable variant fields are however not used here. See
http://caml.inria.fr/pub/docs/manual-caml-light/node4.6.html for
documentation.

In Caml Light, a record may return a mutable location, akin to a lvalue in
C-like languages. For example, with the mutable variant

type foo = Foo of mutable int 

you may write:

let set_foo (f : foo) (n : int) = 
  match f with 
  | Foo loc -> 
     loc <- n 

"foo <- bar" is used here to assign a value "bar" to a lvalue "foo" bound in
a mutable pattern.
In your example, two mutable patterns are used :

 | { tail = Cons(_, ref newtail) as oldtail } -> 
  • oldtail is a mutable pattern denoting the mutable "tail" field of the
    record
  • (ref newtail) is a specific syntax, a pattern on references. It binds a
    mutable pattern "newtail" corresponding to the location of the reference
    In other words, in Caml Light you can write the ":=" operator as such:

    let prefix := r v =
    match r with
    | ref loc ->
    loc <- v

Hope that helps.

.

Edit:

About the strange error message: I think that internally, Caml Light maintain a list of "value identifiers" in the scope, which come from pattern matching a mutable field (record or variant). When they see a foo <- bar expression, they look in that environment to find the corresponding location. Such environment is local to the expression, it never escapes. In particular at toplevel it is empty, and the errors tells you that no "value identifier" (mutable pattern) exists in the scope.

There is another thing: the namespace of value identifiers and usual identifiers are not distinct. When you match a value identifier, Caml Light adds to the scope the value identifier (mutable), but also the corresponding identifier with the matched rvalue. This can be quite confusing as you may mutate the location, but the value won't change :

#match ref 1 with (ref x) -> (x <- 2; x);;
- : int = 1


#match ref 1 with (ref x) as a -> (x <- 2; !a);;
- : int = 2

The (value) identifier will shadow any older identifier (value identifier or not)

#let x = 1 in let (ref x) = ref 2 in x;;
- : int = 2

(If you didn't know, let pattern = e1 in e2 is equivalent to match e1 with pattern -> e2 (except for the type system))

As the syntaxic classes for identifiers and value identifiers are the same, a non-mutable identifier will also shadow a value identifier, giving birth to a different error:

#let (ref x) = ref 2 in let x = 1 in x <- 3;;
Toplevel input:
>let (ref x) = ref 2 in let x = 1 in x <- 3;;
>                                    ^^^^^^
The identifier x is not mutable.
失而复得 2024-10-20 05:06:29

在 OCaml 中,<- 运算符会改变可变字段或对象实例变量(使用 := 改变引用)。然而,还有其他一些事情发生,比如我不熟悉的模式匹配中的 ref 。我认为它向 Caml Light 发出信号,将单元格作为参考进行匹配(类似于 OCaml 中模式匹配中的 lazy),从而产生一个可以作为 <- 用于突变。将变量传递到函数中会传递变量的值,该变量的值是可变的,因此函数不能改变它。

所以:它看起来像匹配新的尾部,因为 ref newtailnewtail 建立为糖化名称,这样评估 newtail 就会转换为 ! newtail' (其中 newtail' 是代表引用本身的一些内部名称)和 newtail <- foo 转换为 newtail' := foo< /代码>。

不过,我实际上并不了解 Caml Light,而且我也不熟悉这种糖分,即使它存在于 OCaml 中(您提供的代码不能在 OCaml 中编译),但这就是我身上发生的事情。

In OCaml, the <- operator mutates mutable fields or object instance variables (references are mutated with :=). However, there are other things going on like the ref in your pattern matching that are unfamiliar to me. I think that it is signalling to Caml Light to match the cell as a reference (analogous to lazy in pattern matches in OCaml), resulting in a variable that is viable as the left-hand side of <- for mutation. Passing the variable into the function passes the value of the variable, which is not mutable, and therefore the function cannot mutate it.

So: it looks like matching the new tail as ref newtail establishes newtail as a sugared name such that evaluating newtail is transformed to !newtail' (where newtail' is some internal name representing the reference itself) and newtail <- foo transforms to newtail' := foo.

I don't actually know Caml Light, though, and I am unfamiliar with this sugaring if it even exists in OCaml (the code you provided does not compile in OCaml) but that's what it looks like is happening to me.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文