0

With lisps (e.g. Scheme), one can test whether or not two list tails are identical:

(define ls '(1 2 3))

(define tail1 (cdr ls))  ; Get the tail of the list.
(define tail2 (cdr ls))

(eqv? tail1 tail2)  ; Check if identical. Returns true.

How can an equivalent be implemented in OCaml using references?

Suppose I have this:

let ls = ref [1; 2; 3]
let tail1 : int list ref = get_tail_ref ls
let tail2 : int list ref = get_tail_ref ls
assert (tail1 == tail2)  (* Ensure that they are identical. *)

Is this a correct approach? How can get_tail_ref be defined?

Flux
  • 9,805
  • 5
  • 46
  • 92
  • This seems like [an XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Lists are immutable, so there's no point in explicitly passing around pointers to it. What are you actually trying to accomplish at a higher level? – glennsl Mar 27 '19 at 13:31

4 Answers4

2

The list type in the OCaml standard library is immutable and you can't do anything with it. Putting a pointer to a list into a mutable cell doesn't make the list itself mutable. Fortunately, you can implement mutable lists, and even follow the lisp data representation, e.g.,

type ('a,'b) cell = {mutable car : 'a; mutable cdr : 'b}

The type system will fight with you though :)

Also, it looks like you're confusing pointers and references. In OCaml, all boxed types (like lists, strings, floats, etc) are represented as pointers. Immediate types, such as int, char, and data constructors, such as None, My_constructor are represented as tagged integers. (It is the same representation all modern lisps are using, btw).

The reference is just a type defined in the standard library as

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

So it is a boxed type, that contains a mutable pointer to an arbitrary value. So, for example, you can put a list there, {contents = [1;2]}, and it will contain a pointer to an immutable list. You can change the contents to point to a different list, but you can't change the list itself. Again, there are immutable, and you can't turn something that is immutable to mutable.

Also, note that OCaml will share data, for example

let common_tail = [3;4]
let head_one = 1 :: common_tail
let head_two = 0 :: common_tail

They will both share the same tail, e.g.,

List.tl head_one == List.tl head_two

Usually, this is quite innocent, since people don't use mutable data types in OCaml a lot, but you can actually create a list of reference they will be shared too, e.g.,

let common_tail = [ref 3; ref 4]
let head_one = ref 1 :: common_tail
let head_two = ref 0 :: common_tail

And if you will now set cadr to 33

List.hd (List.tl head_two) := 33;;

It will affect both lists

# head_one;;
- : int ref list = [{contents = 1}; {contents = 33}; {contents = 4}]

# head_two;;
- : int ref list = [{contents = 0}; {contents = 33}; {contents = 4}]
ivg
  • 34,431
  • 2
  • 35
  • 63
  • Regarding the `common_tail` example, is the sharing of data guaranteed by OCaml? – Flux Mar 28 '19 at 06:42
  • Yes, of course, just because `common_tail` is fundamentally a pointer, and both `head_one` and `head_two` are created using the `common_tail` value, it will be always the same `common_tail`. It is not an optimization or some special behavior. A regular pass-by-reference behavior. – ivg Mar 28 '19 at 11:29
0

If you want to get the tail of a list, simply call List.tl on it. Alternatively, you can use pattern matching to extract the tail. For example, you can write Lisp's nthcdr as follows:

let rec nthcdr n list =
  if (n <= 0) then
    list
  else match list with
       | [] -> []
       | _::list -> (nthcdr (n - 1) list)

You could use it as follows:

let x = [1; 2; 3; 4] in
  assert ((nthcdr 3 x) == (nthcdr 3 x))

In practice, the above function would be wrapped in another one that checks whether N is negative before recursing.

coredump
  • 37,664
  • 5
  • 43
  • 77
0

First of all let ls = ref [1; 2; 3] doesn't make too much sense in this context - it makes ls mutable, but not the list contents itself. Try smth like this instead:

let ls = [1; 2; 3]
let tail1 = List.tl ls (* Note the type is `int list option` here, not `int list` *)
let tail2 = List.tl ls
assert (tail1 = tail2)

Note that using = instead of == in the last line is important - you need semantic equality check, not the physical one (refer to the https://caml.inria.fr/pub/old_caml_site/FAQ/FAQ_EXPERT-eng.html#egalite for the difference explained in details).

Konstantin Strukov
  • 2,899
  • 1
  • 10
  • 14
  • I need a pointer to the tail of the list, and not the tail itself. This is because I will be passing the tail of the list around by reference, and I may need to check the identity of the tails (using `==`). – Flux Mar 27 '19 at 13:15
  • 1
    Note that `==` has very limited semantics when applied to immutable values. It isn't guaranteed to respect an underlying identity for such values. If you depend on extra semantics, your code is probably implementation dependent. – Jeffrey Scofield Mar 27 '19 at 16:52
0

A list in ocaml is always a pointer to the list or []. The tail of a list is again a list so you already have the pointer. A ref on the other hand adds yet another indirection to that. So your ref [1; 2; 3] is actually a pointer to a pointer to a block of memory containing a record of 1 and the address of the tail.

To make a long story short to check if 2 lists have the physically same tail you simply do

List.tl list1 == List.tl list2

This checks for physical equality, checks if they both are the same pointer. Beware that you can have lists that have equal content that are not physically the same. Only lists constructed from the same tail will match.

Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42