1

I came across this implementation of a deque:

type 'a elem = { mutable l1 : 'a elem; mutable l2 : 'a elem; v : 'a option }
type 'a queue = { mutable front : 'a elem; mutable back : 'a elem }

let init () =
    let rec g1 = { l1 = g1; l2 = g2; v = None}
    and     g2 = { l1 = g2; l2 = g1; v = None}
    in
        { front = g1; back = g2 }

let is_empty q =
    let f = q.front
    and b = q.back
    in
        f.l2 == b

let put_between p q x =
    let r = { l1 = p; l2 = q; v = Some x }
    in begin
        if p.l1 == q then p.l1 <- r else p.l2 <- r;
        if q.l1 == p then q.l1 <- r else q.l2 <- r
    end

I don't really understand what's the main idea of this realization, the main concept? Could you please explain it to me? Why are we using those records recursive to each other?

marmistrz
  • 5,974
  • 10
  • 42
  • 94
  • It looks like a doubly-linked list with two sentinal nodes. The two record types aren't mutually recursive though - `elem` is recursive but makes no reference to `queue`. – Lee Nov 27 '15 at 16:31

1 Answers1

3

To expand slightly on what @Lee says, this is a straightforward mutable implementation of a double-ended queue (or deque), like what you would code up in a language with ordinary pointers (such as C).

There are only a few specific ideas I can see, other than keeping the links straight.

  1. There is a header at each end of the deque (what @Lee calls a sentinel). So an empty deque has two nodes in it. Because of two-way linkage, each node points at the other. (This is probably the recursion you're referring to.)

  2. Because OCaml is strongly typed, the nodes all have to be the same type, even the headers at the end. Since headers don't have a value in them, you need to use 'a option for the value. In other words, you need to allow nodes with no value in them.

  3. Caller of put_between needs to supply two adjacent nodes, but they can be supplied in either order.

  4. The code is using "physical equality" (==) to test for identity of nodes. This is a dangerous thing to do in OCaml, but it is correct here. Mutable values can be compared with == with more or less the result you would expect from comparing pointers in an imperative language.

One reason to study OCaml is to learn about functional programming. This code isn't useful for this, since (as I say) it's a mutable implementation. You can see some actual functional deque implementations in Chapter 5 of Chris Okasaki's PhD thesis. (You can also buy his book, which is a perennial favorite.)

advait
  • 6,355
  • 4
  • 29
  • 39
Jeffrey Scofield
  • 65,646
  • 2
  • 72
  • 108
  • But why are we using four links: `front.l1`, `front.l2`, `back.l1`, `back.l2`. In plain C(++) you would simply use two pointers - to the next and previous element – marmistrz Nov 28 '15 at 12:12
  • Each node has two other nodes: `l1` and `l2`. There are two links, not four. A queue object (`'a queue`) contains the two headers, so there are four links total inside the queue object. – Jeffrey Scofield Nov 28 '15 at 16:52
  • Aaaa, so `front` and `back` are dummy objects, `l1` is the previous node and `l2` is the next node? – marmistrz Nov 28 '15 at 19:57
  • 1
    Yes, this is correct. I wouldn't call them "dummy" objects, but they are empty nodes. Using headers simplifies the code slightly, especially in OCaml with its strong typing. (An empty deque has to be the same type as one with some nodes in it.) – Jeffrey Scofield Nov 28 '15 at 20:00
  • Well, but there's something weird in here. The previous element for the `back` one is `back` itself and the `next` one is `front`. Any logic behind it? – marmistrz Nov 29 '15 at 19:48
  • I haven't followed the logic exactly, but I suspect it simplifies the code of `put_between`, making the code a little more elegant. In OCaml, as I said, types are cleaner if the previous element of `back` is always some node. Otherwise you would need to use another `option` type. But even without strong typing these kinds of self-pointers work out nicely. You can find them in Knuth (or elsewhere). – Jeffrey Scofield Nov 29 '15 at 20:06
  • Yes, but we could set it like this: `let rec guard_left = { previous = guard_left; next= guard_right; value = None } and guard_right = {previous = guard_left; next = guard_right; value = None } in {front = guard_left; back = guard_right }` and I believe this is more straightforward to implement – marmistrz Nov 29 '15 at 20:13
  • Sure, but my intuition is that it doesn't buy anything. You mght check if it makes the code for `put_between` any simpler. Circular lists do have some nice properties (also in Knuth btw). But with *two* headers these might not be such a big deal. The bottom line is that there are lots of small changes you can make that don't matter. – Jeffrey Scofield Nov 29 '15 at 20:17