3

Empty lists are ... strange, to a Prolog beginner like myself. I would say that it isn't possible to write an empty list [] as a difference list T1-T2 just as it isn't possible to write an atom as a difference list. However, I would guess that to use recursion, there must be a way to use [] in a difference list setting. I have Google'd for this but I cannot find an answer, and Bratko (Prolog Programming for AI) only briefly touches the subject.

So, is it possible to write an empty list as a difference list in Prolog, if so how and when would it be useful?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Bram Vanroy
  • 27,032
  • 24
  • 137
  • 239
  • 3
    After seeing your other question, [How to use difference lists in a Prolog interpreter](http://stackoverflow.com/q/41591595/1243762), I see that you are reading Bratko. In there it reads `The empty list is represented by any pair of the form L-L.` In your question you have it as `T1-T2`, so are you asking about two different list that are not the same and using them to represent the empty list? Note the emphasis on represent. – Guy Coder Jan 11 '17 at 13:56
  • @GuyCoder DCGs indeed come in handy (we discussed them in class as well), though it still doesn't make the empty list clear to me. The idea and benefit of 'representing' a structure (say [a,b]) as a difference list [a,b|T]-T) escapes me. As I commented [here](http://stackoverflow.com/questions/41591595/how-to-use-difference-lists-in-a-prolog-interpreter/41592716#comment70390280_41592716) it's in particular not clear how concatenating/appending is solved with difference lists since the result is not what you'd want - if I'm not mistaken. – Bram Vanroy Jan 11 '17 at 14:42
  • @GuyCoder 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:46
  • Here is one reference about pointer to end. It is from ["The art of Prolog : advanced programming techniques"](http://www.worldcat.org/oclc/13665520). Using [Google Scholar](https://scholar.google.com/) search the book for [difference list pointer end](https://books.google.com/books?hl=en&lr=&id=w-XjuvpOrjMC&oi=fnd&pg=PR14&dq=The+Art+of+Prolog:+Advanced+Programming+Techniques&ots=4YF-WIC_Ku&sig=KNZHkZAqFHINvTuNzIeP-EUVa8g#v=onepage&q=difference%20list%20pointer%20end&f=false) and you should be on page 286. – Guy Coder Jan 11 '17 at 15:10
  • I would generally use a difference list inside a computation when I need efficient appending. When that computation is finished, I'd probably hand a normal list back to the calling process. This is something `phrase/2` does for you automatically when using DCGs. But this goes for any data structure with holes, not just difference lists—I may need holes (like the end of the list) while computing something but I don't usually want to reason over them in general. – Daniel Lyons Jan 11 '17 at 16:06

2 Answers2

5

Problems with understanding this topic are typically due to using misleading terminology.

As recommended in tutorial.pdf and especially pap95.pdf, use for example list difference or simply difference.

Section 5 of Teaching beginners Prolog contains relevant reasons for this.

The empty list is uniquely denoted by the atom [].

Note that a list difference always means reasoning about two lists, and due to this categorical difference between a single and multiple lists, you can at best find some correspondence or analogy, but not identity between the empty list and a list difference.

I completely support the view expressed in the paper above that you should focus on using DCGs, at least at first. Reasoning about differences explicitly will come naturally later to you.

mat
  • 40,498
  • 3
  • 51
  • 78
0

Appending two list differences means just a unification of first diff's end pointer with the second one's head. With regular lists it requires retracing of the whole list structure of the first list. Thus repeated concatenation on the right is linear with the list difference technique, and quadratic with plain lists.

When all the intended concatenations are done, to return the whole structure as a plain list to a caller we just unify the "end pointer" logvar with [].

In C terms, list difference is a portion of singly-linked list where we maintain two variables: its head pointer but also its tail pointer:

// typedef struct { int payload; node* next } node;
typedef struct { node** head; node** tail } list_diff;

Now each concatenation is just an assignment of the end pointer:

void diff_concat( list_diff* a, list_diff* b)
{
    *(a -> tail) -> next = *(b -> head);
    a -> tail = b -> tail;
}

And finalization is

void diff_finalize( list_diff* a)
{
    *(a -> tail) = NULL;  // or some sentinel value, whatever
}

In Prolog we could represent it as a binary term like -/2, e.g. -(A,B) or A-B.

But (as in C also) there's no actual need to build an actual structure in memory just for holding the two pointers; we can just maintain two logvars individually. Or let DCGs do it for us.

The above was the motivational introduction to list difference technique, "what are they good for?". It also makes clear that the representation of empty difference is

list_diff* d;
*(d -> head) = *(d -> tail);

Or in Prolog, a pair of logvars unified with each other: L-L, var(L). To see why, see what happens when empty diff is appended with some other diff on its right (it is always on the right that we append things, thus growing the lists in the top-down manner). My C may be off here, the idea being that by setting the tail the appending to an empty diff will also update its head.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 2
    BTW, what do you mean by *length(a) == 0 is the same as null(a)*. Is this a critique of Haskell? – false Mar 21 '17 at 10:35
  • @false yes. I want full equational reasoning all the way down. – Will Ness Mar 21 '17 at 17:14
  • Thus, `let a = [1..]; in length(a) == 0` would be a counterexample – false Mar 21 '17 at 17:18
  • @false I want to code "in lists" and have compiler automatically choose the particular data type implementing the operations that I use, most efficiently (so, say, use arrays or flat B-trees, instead). I want to define `primes = [2..] \ ...` (as in my signature), and have the most efficient segmented array code derived by compiler. Ultimately, I want to just speak my mind (in English, etc.). Why are millions of creative people all over the world forced to re-implement mundane tasks over and over in new language du jour, is beyond my understanding. – Will Ness Mar 21 '17 at 17:20
  • @false yes. It should immediately return false. In Haskell, it diverges. – Will Ness Mar 21 '17 at 17:21
  • But there are very soon natural limits, like undecidability. – false Mar 21 '17 at 17:22
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/138662/discussion-between-will-ness-and-false). – Will Ness Mar 21 '17 at 17:23