In general, all data structures (queues, stacks, n-ary trees, etc.) can be implemented using the ideas presented. Moreover, as all data is dynamic (in fact, there is no such concept as ``static variable''), and the user does not need to worry about memory management, dangling pointers, and the like, more sophisticated data structures can be used. Once familiar with the language, the programmer is able to focus on using the best data for the application at hand. Garbage collection is also automatic, so no memory leaks are possible (and, if they happen, they are the language implementor's fault, and not the programmer's).
More refined data structures are possible, using the advantage of having free variables inside the data: this leaves the interesting possibility of incrementally adding more items to the structure, and can be seen as ``open pointers''. Probably using them is not often necessary in CLP, where the focus is more in constraint solving than in building intricate data structure and coding refined algorithms--the solver already contains such algorithms. Notwithstanding, we will give two examples of using open data structures for problems already seen.
insert_ordtree(Element, tree(Element, L, R)):- !. insert_ordtree(Element, tree(This, L, R)):- precedes(Element, This), insert_ordtree(Element, L). insert_ordtree(Element, tree(This, L, R)):- precedes(This, Element), insert_ordtree(Element, R).
There is a trick and something not yet known. The `!' sign is a control primitive, which in this case means that ``after trying this clause, do not try the others below: commit to the decision you have made, even if you fail in the program afterwards'', and is called a cut. The trick is that, as the tree is traversed down, going left or right, using the second and third clauses, there must be a point in which the first clause matches: either we have found the element we want to insert (and there is no need to insert it, then) or we have reached a leaf, which is a free variable, and then it is instantiated and matches the first clause. This variable is bound to a tree/3 structure, with fresh, unbound variables for the right and left sons. Here is an example of using it:
?- insert_ordtree(k, X), insert_ordtree(l, X), insert_ordtree(a, X), insert_ordtree(f, X), insert_ordtree(z, X). X = tree(k,tree(a,_,tree(f,_,_)),tree(l,_,tree(z,_,_)))
We will use difference lists to rewrite the pre_order/2 program without the need of append at the end:
pre_order(Tree, List):- pre_order_open(Tree, List-[]). pre_order_open(void, List-List). pre_order_open(tree(X, Left, Right), [X|PrefLeft]-FinalRest):- pre_order_open(Left, PrefLeft-Pref), pre_order_open(Right, Pref-FinalRest).
Some hints to understand this code:
In real applications, and when this technique is fully understood, the prefix and suffix of the lists are usually passed as separate arguments.