0

Suppose we have a list:

List = nil | Cons(car cdr:List).

Note that I am talking about modifiable lists! And a trivial recursive length function:

recursive Length(List l) = match l with
   | nil => 0
   | Cons(car cdr) => 1 + Length cdr
end.

Naturally, it terminates only when the list is non-circular:

inductive NonCircular(List l) = {
   empty: NonCircular(nil) |
   \forall head, tail: NonCircular(tail) => NonCircular (Cons(head tail))
}

Note that this predicate, being implemented as a recursive function, also does not terminate on a circular list.

Usually I see proofs of list traversal termination that use list length as a bounded decreasing factor. They suppose that Length is non-negative. But, as I see it, this fact (Length l >= 0) follows from the termination of Length on the first place.

How do you prove, that the Length terminates and is non-negative on NonCircular (or an equivalent, better defined predicate) lists?

Am I missing an important concept here?

Necto
  • 2,594
  • 1
  • 20
  • 45

2 Answers2

0

Unless the length function has cycle detection there is no guarantee it will halt!

For a singly linked list one uses the Tortoise and hare algorithm to determine the length where there is a chance there might be circles in the cdr.

It's just two cursors, the tortoise starts at first element and the hare starts at the second. Tortoise moves one pointer at a time while the hare moves two (if it can). The hare will eventually either be the same as the tortoise, which indicates a cycle, or it will terminate knowing the length is 2*steps or 2*steps+1.

Compared to finding cycles in a tree this is very cheap and performs just as well on terminating lists as a function that does not have cycle detection.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
0

The definition of List that you have on top doesn't seem to permit circular lists. Each call to the "constructor" Cons will create a new pointer, and you aren't allowed to modify the pointer later to create the circularity.

You need a more sophisticated definition of List if you want to handle circularity. You probably need to define a Cell containing data value and an address, and a Node which contains a Cell and an address pointing to the previous node, and then you'll need to define the dereferencing operator to go back from addresses to Cells. You can also try to define non-circular on this object.

My gut feeling is that you will also need to define an injective function from the "simple" list definition you have above to the sophisticated one that I've outlined and then finally you'll be able to prove your result.

One other thing, the definition of NonCircular doesn't need to terminate. It isn't a program, it is a proof. If it holds, then you can examine the proof to see why it holds and use this in other proofs. Edit: Thanks to Necto for pointing out I'm wrong.

Pramod
  • 9,256
  • 4
  • 26
  • 27
  • I agree except for the last paragraph. The termination is essential for proof-programs. otherwise here is a proof for false: `f : true -> false := {return f();}` – Necto Oct 12 '15 at 16:36