1

When I was writing down this question on an empty list as a difference list I wanted to test what I knew about those structures. However, when I tried something as simple as comparing different notations it seemed that I was wrong and that I did not understand what is actually going on with difference lists.

?- L = [a,b,c|[d,e]]-[d,e], L = [a,b,c].
false % expected true

I tested this on SWI-Prolog as well as SICStus. I verified the notation as this is how it is written in Bratko's Prolog Programming for AI, page 210, but apparently unification is not possible. Why is that? Don't these notations have the same declarative meaning?

Community
  • 1
  • 1
Bram Vanroy
  • 27,032
  • 24
  • 137
  • 239
  • 2
    The key to reading that section is the word *represent*. If you look in the book, when the examples can be used with the top level, AKA interpreter, they are prefixed with `?-`. In that section it reads `So the list [a,b,c] can be represented, for example, by [a,b,c] - [] or [a,b,c,d,e] - [d,e] or [a,b,c,d,e|T] - [d,e|T] or [a,b,c|T]-T where T is any list.` So it is not an example to run with the top level but examples that *represent* what you should be thinking, thus the error when using it as input in the top level. – Guy Coder Jan 11 '17 at 13:52
  • 1
    `L` cannot both be `[a,b,c|[d,e]]-[d,e]` and `[a,b,c]`. These are two different structures. – lurker Jan 11 '17 at 13:53
  • @lurker I see that now, but as clarified in the comments that's probably why I don't see the practical benefit of the difference list. You'd always need the *finalize* step as suggested by Willem Van Onsem if you want to use the concatenated list. – Bram Vanroy Jan 11 '17 at 14:50
  • That is true. The difference list has a "hole" in it that eventually must be "filled" if you need a final, ground result. – lurker Jan 11 '17 at 15:21
  • Of interest: [Source code from Bratko book](http://www.pearsoned.co.uk/highereducation/resources/bratkoprologprogrammingforartificialintelligence3e/) – Guy Coder Jan 11 '17 at 19:45

1 Answers1

5

I think you have the idea that the Prolog interpreter treats difference lists as something special. That is not the case: Prolog is not aware of the concept of a difference list (nor of nearly every concept except some syntactical sugar). He only sees:

L=-( |(a, |(b, |(c, |(d, |(e, []))))), |(d, |(e, [] )))

where -/2 and |/2 are functors, and a, b, c, d, e and [] are constants.

Difference lists are simply a programming technique (like for instance dynamic programming is a technique as well, the compiler cannot detect nor treat dynamic programming programs differently). It is used to efficiently unify a (partially) ununified part deep in an expression.

Say you want to append/3 two lists. You can do this as follows:

%append(A,B,C).
append([],L,L).
append([H|T],L,[H|B]) :-
    append(T,L,B).

But this runs in O(n): you first need to iterate through the entire first list. If that list contains thousands of elements, it will take a lot of time.

Now you can define yourself a contract that you will feed an append_diff/3 not only the list, but a tuple -(List,Tail) where List is a reference to the beginning of the list, and Tail is a reference to the end of the not unified list. Examples of structures that fulfill this requirement are Tail-Tail, [a|Tail]-Tail, [1,4,2,5|Tail]-Tail.

Now you can effectively append_diff/3 in O(1) with:

append_diff(H1-T1,T1-T2,H1-T2).

Why? Because you unify the ununified tail of the first list with the second list. Now the ununified tail of the second lists becomes the tail of the final list. So take for instance:

append_diff([a|T1]-T1,[1,4,2,5|T2]-T2,L).

If you call the predicate, as you see above, T1 will unify with [1,4,2,5|T2], so now the first list collapses to [a|[1,4,2,5|T2]] or shorter [a,1,4,2,5|T2], since we also have a reference to T2, we can "return" (in Prolog nothing is returned), [a,1,4,2,5|T2]-T2: a new difference list with an open tail T2. But this is only because you give - a special meaning yourself: for Prolog - is simply -, it is not minus, it does not calculate a difference, etc. Prolog does not attach semantics to functors. If you would have used + instead of -, that would not have made the slightest difference.

So to return back to your question: you simply state to Prolog that L = -([a,b,c,d,e],[d,e]) and later state that L = [a,b,c]. Now it is clear that those two expressions cannot be unified. So Prolog says false.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • I now understand why they do not unify, but I do not understand how append_diff/3 would be useful in practice. I mean, if that's how you do appending, the result would be `[a,1,4,2,5|T2]-T2`. You still do not have the value that you want, i.e. `[a,1,4,2,5]`, right? – Bram Vanroy Jan 11 '17 at 14:39
  • @BramVanroy: well you could define a predicate `finalize(A-[],A).` that "finalizes" the diff list. If you then call `finalize([a,1,4,2,5|T2]-T2,Result).` `Result` will be `[a,1,4,2,5]`. But as said before, it is all a matter on how you interpret data yourself. You can interpret `[a,1,4,2,5|T2]-T2` as a list *equivalent* to `[a,1,4,2,5]`. – Willem Van Onsem Jan 11 '17 at 14:42
  • But Prolog doesn't interpret the structure like that, so the `finalize/2` step would be required to have a workable concatenated list, right? (Following comment as posted on other question.) As an example, we want to append [a,b] and [ c,d]. Proposed solution: `conc_d([a,b|T]-T, [d,e|T1]-T1, L).` However, the result would be `L = [a,b,c,d|T1]-T1`, but as a programmer I would need `[a,b,c,d]`, plain and simple. I have a feeling I am missing the point, but I don't see the practical benefit or fail to see how to use difference lists in practice. – Bram Vanroy Jan 11 '17 at 14:48
  • 5
    @BramVanroy: well you could use `conc_d([a,b|T]-T, [d,e|T1]-T1, Temp),finalize(Temp,L)` and then process `L` which is `[a,b,c,d]`. The point is mainly efficiency: the algorithm will run in O(1) instead of O(n). But as said before Prolog does not interpret any structure: the semantics of data only exists by the *grace* of the programmer, not the functors themselves. Note that you do not have to use `finalize/2`: you could simply write a program where all clauses take diff-lists as parameters. You can do analysis and processing on diff-lists like you would do with simple lists. – Willem Van Onsem Jan 11 '17 at 14:52