Reason for no interruption
First, let's try to understand why your definition is not reversible. I will use failure-slice to better explain this. Therefore, consider the query tree_len(X,1). At first glance, everything is perfect, you will even get a good answer!
?- tree_len(T,1). T = n(_G267, leaf, leaf)
But never ask for another answer, because it will loop:
?- tree_len(T,1). T = n(_G267, leaf, leaf) ; ** LOOPS **
So, the answer we got was a little distracted. At first glance, it looked as if everything was in order, and only on the retreat did the real problem arise. This is something to get used to in Prolog. Obviously your query using 3 was better selected. But it was luck.
There is an easy way to ensure this as a whole. Just add an additional false target to the request. Adding false means that we are no longer interested in any answers, since it can no longer be successful. Thus, all the distraction is removed, and we are directly faced with the problem:
?- tree_len(T,1), false. ** LOOPS **
So where does this cycle come from?
In clean, monotonous Prolog programs (such as this one), we can localize the cause without interruption by adding several false targets to our program. If the resulting program (called failure-slice ) does not exit, then the original program does not exit either. This is the minimum failure fragment for our request:
? - tree_len (T, 1), false .
tree_len (n (_, leaf, leaf), 1): - false .
tree_len (n (op, G, D), N): -
tree_len (G, TG), false ,
tree_len (D, TD) ,
N is TG + TD .
There is a bit of our program left! It is this tiny fragment that is responsible for non-execution. If we want to fix the problem, we have to do something in this tiny part. Everything else is useless.
So, we need to somehow change the program so that this fragment no longer sings.
In fact, we have two options: we can use the successor of arithmetic or such as clpfd . The former is available on any Prolog system, the latter is provided only in some versions, such as SWI, YAP, SICStus, GNU, B.
Now 3 is represented by s(s(s(0))) .
tree_lensx (T, s (N)): -
tree_lendiff (T, N, 0).
tree_lendiff (n (_, leaf, leaf), N, N).
tree_lendiff (n (op, G, D), s (N0), N): -
tree_lendiff (G, N0, N1),
tree_lendiff (D, N1, N).
I have used several common coding methods here.
Differences
The actual tree_lendiff/3 ratio, which is a natural number, is not one argument, but rather two. The actual number is the difference between the two. In this way, a reversible definition can be preserved.
Avoiding Left Recursion
Another method was to avoid left recursion. The length described by tree_lendiff/3 is actually equal to the length minus one. Remember that we have the first glitch? Here, too, will be the same failure-slice! However, by “shifting” the length by one, the head of the recursive rule now provides completion.
Initially, restrictions on finite domains for solving combinatorial problems were limited. But you can use them to obtain reversible arithmetic. Implementation in SWI and YAP even goes so far that it compiles into code, which is often equal to the performance of traditional irreversible (is)/2 , while still being reversible.
:- use_module(library(clpfd)). tree_fdlen(n(_,leaf,leaf),1). tree_fdlen(n(op,G,D),N) :- N
This program more closely matches your original definition. However, pay attention to the two goals TG #>= 1 and TD #>= 1 , which added redundant information to ensure the completion of this program.
Now we can list all trees of a certain range as follows:
?- Size in 0..4, tree_fdlen(T, Size). Size = 1, T = n(_A,leaf,leaf) ; Size = 2, T = n(op,n(_A,leaf,leaf),n(_B,leaf,leaf)) ; Size = 3, T = n(op,n(_A,leaf,leaf),n(op,n(_B,leaf,leaf),n(_C,leaf,leaf))) ; Size = 4, ... ; Size = 4, ... ; Size = 3, ... ; Size = 4, ... ; Size = 4, ... ; Size = 4, ... ; false.
Pay attention to the exact order of the substitution of the answer! It doesn't just go 1,2,3,4! Rather, the answers are found in some order. It doesn’t matter which one, so far we are only interested in finding all the solutions!