(OCaml) Странный синтаксис, используемый в queue.ml - оператор `<-`
Просматривая библиотеку 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
должен быть типа 'a queue cell
, так как он получен с помощью Cons(_, ref newtail)
в сопоставлении с образцом: если я правильно понимаю, это будет означать, что newtail
связывает значение, указанное вторым членом tail
поле записи (которое изначально является ссылкой).
Так что же newtail <- c
средства? Если я попытаюсь заменить это утверждение (fun x -> x <- c) newtail
, Я получил The identifier 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
?
Даже незнакомец: следующий код возвращает The value identifier a is unbound.
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.
2 ответа
Смотрите мой ответ в списке Caml
Зачем задавать один и тот же вопрос дважды в разных местах? Это только приводит к дублированию усилий, а знающие люди тратят свое время, чтобы ответить вам. Если вы хотите сделать это, пожалуйста, по крайней мере, опубликуйте перекрестные ссылки (от вашего сообщения stackru в архив списка и наоборот [1]), чтобы люди могли проверить, что он еще не ответил в другом месте.
[1] да, вы можете иметь циклические перекрестные ссылки, так как сообщение stackru изменчиво!
Семантика изменяемых полей и ссылок сильно изменилась (навсегда) между Caml Light и Objective Caml. Помните, что этот код является специфическим для Caml Light - и если вы хотите изучать Caml, вам лучше использовать Objective Caml, реализация которого все еще поддерживается. В Objective Caml изменяются только поля записей. Ссылки являются производным понятием, тип 'a ref определяется как:
type 'a ref = { mutable contents : 'a }
Вы изменяете изменяемое поле с помощью синтаксиса foo.bar <- baz (где "bar" - это поле записи, а foo и baz - любое выражение, foo - типа записи)
В Caml Light поля записи являются изменяемыми, но поля типа суммы (варианты) также изменяемы; изменяемые варианты полей, однако, здесь не используются. См. http://caml.inria.fr/pub/docs/manual-caml-light/node4.6.html для документации.
В Caml Light запись может возвращать изменяемое местоположение, похожее на lvalue в C-подобных языках. Например, с изменяемым вариантом
type foo = Foo of mutable int
Вы можете написать:
let set_foo (f : foo) (n : int) = match f with | Foo loc -> loc <- n
"foo <- bar" используется здесь для присвоения значения "bar" lvalue "foo", связанному с изменяемым шаблоном. В вашем примере используются два изменяемых шаблона:
| { tail = Cons(_, ref newtail) as oldtail } ->
- oldtail - изменчивый шаблон, обозначающий изменяемое поле "tail" записи
(ref newtail) - это особый синтаксис, шаблон ссылок. Он связывает изменяемый шаблон "newtail", соответствующий местоположению ссылки. Другими словами, в Caml Light вы можете написать оператор ":=" следующим образом:
префикс let:= r v = соответствует r с | ref loc -> loc <- v
Надеюсь, это поможет.
,
Редактировать:
О странном сообщении об ошибке: я думаю, что внутренне, Caml Light поддерживает список "идентификаторов значений" в области видимости, которые происходят из шаблона, соответствующего изменяемому полю (запись или вариант). Когда они видят foo <- bar
выражение, они смотрят в этой среде, чтобы найти соответствующее местоположение. Такая среда является локальной для выражения, она никогда не ускользает. В частности, на верхнем уровне он пуст, и ошибки говорят о том, что в области действия не существует "идентификатор значения" (изменяемый шаблон).
Есть еще одна вещь: пространство имен идентификаторов значений и обычные идентификаторы не различаются. Когда вы сопоставляете идентификатор значения, Caml Light добавляет в область действия идентификатор значения (изменяемый), а также соответствующий идентификатор с соответствующим значением r. Это может быть довольно запутанным, так как вы можете изменить местоположение, но значение не изменится:
#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
(Если вы не знали, let pattern = e1 in e2
эквивалентно match e1 with pattern -> 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.
В OCaml <-
оператор изменяет изменяемые поля или переменные экземпляра объекта (ссылки изменяются с :=
). Тем не менее, есть и другие вещи, такие как ref
в вашем сопоставлении с образцом, которые мне незнакомы. Я думаю, что это сигнализирует Caml Light, чтобы соответствовать ячейке в качестве ссылки (аналогично lazy
в сопоставлении с образцом в OCaml), в результате чего переменная, которая является жизнеспособной в качестве левой части <-
для мутации. Передача переменной в функцию передает значение переменной, которое не является изменяемым, и, следовательно, функция не может изменить его.
Итак: похоже на соответствие нового хвоста как ref newtail
налаживает newtail
как подслащенное имя, такое, что оценивая newtail
превращается в !newtail'
(где newtail'
какое-то внутреннее имя, представляющее саму ссылку) и newtail <- foo
превращается в newtail' := foo
,
Хотя я на самом деле не знаю Caml Light, и мне незнакомо это засахаривание, если оно вообще существует в OCaml (код, который вы предоставили, не компилируется в OCaml), но, похоже, это происходит со мной.