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.
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!
.
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 <- barexpression, 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 :
The (value) identifier will shadow any older identifier (value identifier or not)
(If you didn’t know,
let pattern = e1 in e2is equivalent tomatch 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: