Need help understanding Prolog append/3 and inverse/2 and trace output - algorithm

This is the question
find value A.
inverse([],[]).
inverse([H|T],D) :-
inverse(T,Z),
append(Z,[H],D).
append([],X,X).
append([X|L],M,[X|N]) :-
append(L,M,N).
This is the answer:
Plese help me to understand this!

The images of the Prolog code you posted show some unusual or very old Prolog, in particular for list the use of [H:T] is now done as [H|T], notice the change from : to |, and <= is more common as :-.
To understand the Prolog code it is easier to start from the bottom up. I will not cover unification or backward chaining in this as going to that level of detail would require a chapters worth here.
The first predicate to understand is append/3. Normally you never see the code for append given as it is a built-in predicate, but here it is given.
Append/3 has three parameters which are all list. The first two are appended together to form the third.
?- append_01([],[],R).
R = [].
?- append_01([a],[],R).
R = [a].
?- append_01([],[a],R).
R = [a].
?- append_01([a],[b],R).
R = [a, b].
but Prolog predicates can have other modes of operation which can bind values to what would be considered input parameters in other programming languages, e.g.
?- append(X,[b],[a,b]).
X = [a] ;
false.
?- append_01([a],Y,[a,b]).
Y = [b].
?- append(X,Y,[a,b]).
X = [] , Y = [a, b] ;
X = [a] , Y = [b] ;
X = [a, b], Y = [] ;
false.
or can just be used to verify the arguments
?- append([a],[b],[a,b]).
true.
?- append([a],[c],[a,b]).
false.
Next is the predicate inverse/2 which is more commonly known in Prolog as reverse/2, and again the source code is given here.
This simply takes one list and reverses it, e.g.
?- inverse([],X).
X = [].
?- inverse([a],X).
X = [a].
?- inverse([a,b],X).
X = [b, a].
however this version of the source code does not do well in other modes, e.g.
?- inverse(X,[]).
X = [] ;
Action (h for help) ? abort
% Execution Aborted
but that doesn't matter to answer the question.
The next part of what you posted is a trace of the execution of the query
?- inverse([[1,2,3],[5,4]],A).
In order to use the trace on your code, since there is a built-in predicate for append/3 I had to rename the predicate. Here is the code I used.
inverse([],[]).
inverse([H|T],D) :-
inverse(T,Z),
append_01(Z,[H],D).
append_01([],X,X).
append_01([X|L],M,[X|N]) :-
append_01(L,M,N).
Using SWI-Prolog
set up the trace
?- visible(+all),leash(-all).
start the trace
trace.
execute the query
[trace] ?- inverse([[1,2,3],[5,4]],A).
returns
Call: (8) inverse([[1, 2, 3], [5, 4]], _7548)
Unify: (8) inverse([[1, 2, 3], [5, 4]], _7548)
Call: (9) inverse([[5, 4]], _7794)
Unify: (9) inverse([[5, 4]], _7794)
Call: (10) inverse([], _7794)
Unify: (10) inverse([], [])
Exit: (10) inverse([], [])
Call: (10) append_01([], [[5, 4]], _7802)
Unify: (10) append_01([], [[5, 4]], [[5, 4]])
Exit: (10) append_01([], [[5, 4]], [[5, 4]])
Exit: (9) inverse([[5, 4]], [[5, 4]])
Call: (9) append_01([[5, 4]], [[1, 2, 3]], _7548)
Unify: (9) append_01([[5, 4]], [[1, 2, 3]], [[5, 4]|_7792])
Call: (10) append_01([], [[1, 2, 3]], _7792)
Unify: (10) append_01([], [[1, 2, 3]], [[1, 2, 3]])
Exit: (10) append_01([], [[1, 2, 3]], [[1, 2, 3]])
Exit: (9) append_01([[5, 4]], [[1, 2, 3]], [[5, 4], [1, 2, 3]])
Exit: (8) inverse([[1, 2, 3], [5, 4]], [[5, 4], [1, 2, 3]])
A = [[5, 4], [1, 2, 3]].
I will not explain the details of a trace as other SO Q&A do that.
The trace you posted also has more detail than generated by using the trace, e.g. the bindings (θ).
To see the bindings use gtrace/0
?- gtrace.
% The graphical front-end will be used for subsequent tracing
true.
then execute the query
[trace]?- inverse([[1,2,3],[5,4]],A).
and press space bar to single step. You will have to experiment with it to learn how it works; AFAIK there is no posted documentation on how to use it.
From OP comment:
There are some replacements from letters to numbers and theta symbol that make me hard to understand.
While the bindings (θ) are more specific to logic languages, the numbers are also seen in stack based functional languages, see De Bruijn index. Also instead of writing the bindings using vertical line separator (---) , I prefer to use (↦) as seen here.
The lines 1 -4 are just the source code stated again.
Normally with a trace the goal is to covey a tree structure of the executions (calls) but with these lines unless you know how Prolog works it is really hard to see that there is a tree structure.
The lines with the over-bars are meant to help understand what is going on, but if you just follow the flow of the executions (calls) then you may find as I do that they just cause confusion and can be ignored.
At you noted in the comments the Res(_,_) are referring to previous lines in the trace. So Res(5,2) on line 6 can be read as Line 6 is the result of a call from line 5 and which then calls line 2.
The unifications or bindings (θ) are show as as sets. I am not exactly sure what the super and sub script numbers represent but they are clearly linked to De Bruijn indexes. You will have to ask your teacher to explain the super and sub scripts.
After trying several times to explain this with just text, I finally resorted to using Microsoft Visio to do it as graphical tree which was much easier, faster and more accurate.
Even though it was not needed, I added the line trace output from SWI-Prolog into the image and then placed only the call lines in the corresponding places in the tree so that if you want to correlate the two you can. I did it for myself as a check to make sure it was correct.
I would not be surprised if there are a few typo mistakes as I had to redo parts of it many times to make it easy to comprehend. Hopefully I achieved that goal.

Related

Prolog - Recursive append to list returning false

Title says it all, but here we are again. Trying to append recursively to a list in Prolog, and while I have previously gotten it to work by having "temporary buffers" (via nb_setval/nb_getval) I'd like to learn how to, in a slightly more appropriate way, recursively append to lists.
I've understood Prolog works all around bindings and once something is bound it's difficult to manipulate it, so initially I sat with this, but I've understood why that does not quite work:
recursiveAppend([], _).
recursiveAppend([H|T], Output):-
append(H, [], Output),
recursiveAppend(T, Output).
That made me change the code and go to the following:
recursiveAppend([], _).
recursiveAppend([H|T], Output):-
append(H, Output, NewOutput),
recursiveAppend(T, NewOutput).
Which I had hoped would work, as it made sense to myself and apparently to others while scouring other StackOverflow questions as well. Unfortunately, calling this predicate in SWI-Prolog only returns false.
?- recursiveAppend([1, 2, 3, 4, 5], L1). false
Expected/desired result would, in this case, be:
?- recursiveAppend([1, 2, 3, 4, 5], L1). L1 = [1, 2, 3, 4, 5].
For the sake of clarification, the runtime of the program should look something like this if "fleshed out":
recursiveAppend([H|T], Output):-
% H is 1, Output is []
append(H, Output, NewOutput),
% NewOutput is [1]
recursiveAppend(T, NewOutput).
recursiveAppend([H|T], Output):-
% H is 2, Output is [1]
append(H, Output, NewOutput),
% NewOutput is [1, 2]
recursiveAppend(T, NewOutput).
recursiveAppend([H|T], Output):-
% H is 3, Output is [1, 2]
append(H, Output, NewOutput),
% NewOutput is [1, 2, 3]
recursiveAppend(T, NewOutput).
recursiveAppend([H|T], Output):-
% H is 4, Output is [1, 2, 3]
append(H, Output, NewOutput),
% NewOutput is [1, 2, 3, 4]
recursiveAppend(T, NewOutput).
recursiveAppend([H|T], Output):-
% H is 5, Output is [1, 2, 3, 4]
append(H, Output, NewOutput),
% NewOutput is [1, 2, 3, 4, 5]
recursiveAppend(T, NewOutput).
recursiveAppend([], _). % First argument (list) is empty, and the second argument (list) has been populated (with [1, 2, 3, 4, 5]), program done.
Any and all help is appreciated, even though this question has probably been asked a million times before!
"Recursive append" is not something that often makes sense in Prolog. The question should include information about what problem you are trying to solve. Currently it is not about that; it is about how you are trying to solve your problem. That "how" is "recursive append", but this is almost certainly not how you should really solve that problem. We could offer better help if we knew what the problem was, not how you think you want to solve it.
Taking the example from the question and the solution from https://stackoverflow.com/a/64092447/4391743:
?- recursiveAppend([1, 2, 3], Ys).
Ys = [1, 2, 3].
?- recursiveAppend(Xs, [1, 2, 3]).
Xs = [1, 2, 3] ;
% nontermination after first answer
?- recursiveAppend([1, 2, X], [A, B, 3]).
X = 3,
A = 1,
B = 2.
?- recursiveAppend([1, 2 | Rest], [A, B, 3, 4]).
Rest = [3, 4],
A = 1,
B = 2 ;
% nontermination after first answer
If this is what you want, then what you seem to want is a "list copy" predicate. Here's a shorter, faster, more complete one:
list_copy([], []).
list_copy([X | Xs], [X | Ys]) :-
list_copy(Xs, Ys).
This doesn't have the non-termination issues that the above predicate has:
?- list_copy([1, 2, 3], Ys).
Ys = [1, 2, 3].
?- list_copy(Xs, [1, 2, 3]).
Xs = [1, 2, 3].
?- list_copy([1, 2, X], [A, B, 3]).
X = 3,
A = 1,
B = 2.
?- list_copy([1, 2 | Rest], [A, B, 3, 4]).
Rest = [3, 4],
A = 1,
B = 2.
If one of the arguments is a list and the other is a variable, a new list structure will be built up and bound to this variable.
But... why do you need a new list structure at all? In pure Prolog you can't tell whether two terms are the same (i.e., sharing the same memory location) or "just" structurally equal. Neither do (or should) you usually care. (There are uses for knowledge about sharing, and about explicit copying, in non-pure Prolog, but again we don't know what you're trying to do.)
So if we can't tell whether a "copy" is indeed a copy or just "an equal term", then we don't need to copy at all. We can get the exact same behavior as above with just unification:
?- [1, 2, 3] = Ys.
Ys = [1, 2, 3].
?- Xs = [1, 2, 3].
Xs = [1, 2, 3].
?- [1, 2, X] = [A, B, 3].
X = 3,
A = 1,
B = 2.
?- [1, 2 | Rest] = [A, B, 3, 4].
Rest = [3, 4],
A = 1,
B = 2.
No copying and certainly no "recursive append" is needed to achieve unification, Prolog knows how to do unification for you.
If this is not what you want, please tell us what the actual problem is. "Recursive append" is almost certainly not it.
Prolog is a different programming paradigm. It requires you to "forget" all you know about programming and learn with an open mind. Don't try to learn Prolog while using "ordinary" variables and reaffecting different values, Prolog variables has only one value or none. They may take different values only on backtracking, and trying to find another set of values to all variables in your program that satisfies all the given predicates.
Suggest you to read books like "learn Prolog Now". Numerous tutorials from state universities are available free on the internet.
Based on your latest edit giving an example to Calling recursiveAppend, here's a code conform with the example.
recursiveAppend(X, Y) :- recursiveAppend(X, [], Y).
recursiveAppend([], X, X).
recursiveAppend([H|T], Current, Output):-
append(Current, [H], NewTemp),
recursiveAppend(T, NewTemp, Output).
Your earlier codes returned false because append expects lists as arguments. So appending an integer (item of input list) will Always fail. I created a version recursiveAppend/3 to accumulate current list in the second arg. At the end of the list, the current list becomes the final output. Will you test it further with more examples and tell us if it is working as required.

Prolog tries to find other solutions but I don't know why (there is only one)

I read Learn Prolog Now website and I try to do the following exercise:
6.5 Write a predicate swapfl(List1,List2) which checks whether List1 is identical to List2 , except that the first and last elements are exchanged. Here’s where append/3 could come in useful again, but it is also possible to write a recursive definition without appealing to append/3 (or any other) predicates.
I've written it and it works correctly, but it tries to find more than one solution.
swapfl([HL1|TL1],[HL2|TL2]):-
swpfl(TL1,TL2,HL2,HL1).
swpfl([HL1|TL1],[HL2|TL2],AccEL1,AccEL2):-
swpfl(TL1,TL2,AccEL1,AccEL2),
HL1=HL2.
swpfl([H1],[H2],H1,H2).
I look at the code and the trace
[trace] [5] ?- swapfl([1,2,3],[3,2,1]).
Call: (51) swapfl([1, 2, 3], [3, 2, 1]) ? creep
Call: (52) swpfl([2, 3], [2, 1], 3, 1) ? creep
Call: (53) swpfl([3], [1], 3, 1) ? creep
Exit: (53) swpfl([3], [1], 3, 1) ? creep
Call: (53) 2=2 ? creep
Exit: (53) 2=2 ? creep
Exit: (52) swpfl([2, 3], [2, 1], 3, 1) ? creep
Exit: (51) swapfl([1, 2, 3], [3, 2, 1]) ? creep
true ;
Redo: (52) swpfl([2, 3], [2, 1], 3, 1) ? creep
Fail: (52) swpfl([2, 3], [2, 1], 3, 1) ? creep
Fail: (51) swapfl([1, 2, 3], [3, 2, 1]) ? creep
false.
and I cannot understand why prolog thinks that it may be worth to start Redo part.
Could someone explain why there is an unsearched branch of the solution tree?
Prolog will always explore all options, before making it's final decision.
Try looking at the following stack overflow question:
Prolog: stop condition?
The false response can appear inconsistent to beginning Prolog programmers and "feel" like an error or warning, but it's actually a perfectly normal Prolog response. Prolog is quite consistent in its behavior of attempting to find solutions and, when no more choices exist, will return false. If Prolog has exhausted all the other choices before it finds the final solution, it displays the solution and doesn't return false.

Prolog Recursion and Variables

I have this insertion sort to sort a list in descending order in Prolog and it works:
insert(X,[],[X]).
insert(X, [Y|Tail], [X,Y|Tail]):- X > Y, !.
insert(X, [Y|Tail], [Y|NTail]):- insert(X, Tail, NTail).
ins_sort([], []).
ins_sort([X|Tail], Sorted):- ins_sort(Tail, STail), insert(X, STail, Sorted).
I am running it on SWISH and trying to understand how it functions with the following trace:
Call:ins_sort([1, 2, 3, 4, 5], _12162)
Call:ins_sort([2, 3, 4, 5], _12358)
Call:ins_sort([3, 4, 5], _12358)
Call:ins_sort([4, 5], _12358)
Call:ins_sort([5], _12358)
Call:ins_sort([], _12358)
Exit:ins_sort([], [])
Call:insert(5, [], _12360)
Exit:insert(5, [], [5])
Exit:ins_sort([5], [5])
Call:insert(4, [5], _12366)
Call:4>5
Fail:4>5
Redo:insert(4, [5], _12370)
Call:insert(4, [], _12282)
Exit:insert(4, [], [4])
Exit:insert(4, [5], [5, 4])
Exit:ins_sort([4, 5], [5, 4])
Call:insert(3, [5, 4], _12378)
Call:3>5
Fail:3>5
Redo:insert(3, [5, 4], _12382)
Call:insert(3, [4], _12294)
Call:3>4
Fail:3>4
Redo:insert(3, [4], _12294)
Call:insert(3, [], _12300)
Exit:insert(3, [], [3])
Exit:insert(3, [4], [4, 3])
Exit:insert(3, [5, 4], [5, 4, 3])
Exit:ins_sort([3, 4, 5], [5, 4, 3])
Call:insert(2, [5, 4, 3], _12396)
Call:2>5
Fail:2>5
Redo:insert(2, [5, 4, 3], _12400)
Call:insert(2, [4, 3], _12312)
Call:2>4
Fail:2>4
Redo:insert(2, [4, 3], _12312)
Call:insert(2, [3], _12318)
Call:2>3
Fail:2>3
Redo:insert(2, [3], _12318)
Call:insert(2, [], _12324)
Exit:insert(2, [], [2])
Exit:insert(2, [3], [3, 2])
Exit:insert(2, [4, 3], [4, 3, 2])
Exit:insert(2, [5, 4, 3], [5, 4, 3, 2])
Exit:ins_sort([2, 3, 4, 5], [5, 4, 3, 2])
Call:insert(1, [5, 4, 3, 2], _12162)
Call:1>5
Fail:1>5
Redo:insert(1, [5, 4, 3, 2], _12162)
Call:insert(1, [4, 3, 2], _12336)
Call:1>4
Fail:1>4
Redo:insert(1, [4, 3, 2], _12336)
Call:insert(1, [3, 2], _12342)
Call:1>3
Fail:1>3
Redo:insert(1, [3, 2], _12342)
Call:insert(1, [2], _12348)
Call:1>2
Fail:1>2
Redo:insert(1, [2], _12348)
Call:insert(1, [], _12354)
Exit:insert(1, [], [1])
Exit:insert(1, [2], [2, 1])
Exit:insert(1, [3, 2], [3, 2, 1])
Exit:insert(1, [4, 3, 2], [4, 3, 2, 1])
Exit:insert(1, [5, 4, 3, 2], [5, 4, 3, 2, 1])
Exit:ins_sort([1, 2, 3, 4, 5], [5, 4, 3, 2, 1])
I get lost once I get beyond the first "Exit". I understand all of the recursive calls until we get to an empty list, which stops the recursive calls because of the other fact and begins going back, but why after the first exit on line 7 does the undefined STail become an empty list [] in the insert call?
Has the exit of ins_sort([], []) set STail to an empty set [] and does this mean that the last argument of a fact is a return value or something?
Traces are much too hard. Re-writing is usually much easier, especially with the deterministic predicates like you have here. Long variable names are also too distracting. Instead of reading and remembering, it just might be easier simply seeing:
insert(X, [], [X]). %1
insert(X, [Y|T], [X,Y|T] ):- X > Y, !. % X was indeed greater than Y: %2
% accept the solution and stop; %3
insert(X, [Y|T], [ Y|NT]):- insert(X, T, NT). % otherwise, insert into tail. %4
%5
ins_sort( [], []). % rule {1} %6
ins_sort( [X|T], S):- % rule {2} %7
ins_sort( T, ST), %8
insert( X, ST, %9
S). %10
Let's try it with a shorter list,
ins_sort([1, 2, 3], S) ? S. %11
= \ / %12
{2: [X1|T1]=[1,2,3] } / %13
ins_sort(T1, ST1), insert(X1, ST1, S). %14
= \ / %15
{2: [X2|T2]=T1=[2,3] } / %16
ins_sort(T2, ST2), insert(X2, ST2, ST1). %17
= \ / %18
{2: [X3|T3]=T2=[3] } / %19
ins_sort(T3, ST3), insert(X3, ST3, ST2). %20
= \ / %21
{1: T3=[] ST3=[] }. %22
and we go by the V-shaped trace from the top left corner down to the middle, winding up the recursion until we reach the base case, and then up and to the right while unwinding the recursion and building the result on our way back from the base case, as usual. Thus we proceed to establish, from the bottom up,
ST3 = []. %22
insert( {X3=3}, {ST3=[]}, ST2 ):- ST2 = [3]. %20
insert( {X2=2}, {ST2=[3]}, ST1 ):- ST1 = [3,2]. %17
insert( {X1=1}, {ST1=[3,2]}, S ):- S = [3,2,1]. %14
And that's that.
I think the problem here is you are having some difficulty understanding what happens with variables during recursion. Let's take a simplified case:
count([], 0).
count([X|Xs], N) :- count(Xs, N0), succ(N0, N).
What happens when I call count([a,b], N) is this:
count([a, b], N)
+-> count([b], N)
The first thing we have to do upon entering count([a,b], N) is a recursive call to count/2. When Prolog re-enters count, we suddenly have a new set of bindings for X and Xs. In the outer call, X=a and Xs=[b], but in the inner call, X=b and Xs=[]. There will then be a third inner call, which begins with the Xs value []. This corresponds to the first three lines of this trace:
Call: (8) count([a, b], _8636) ? creep
Call: (9) count([b], _8866) ? creep
Call: (10) count([], _8866) ? creep
What the tracer is telling you here is "I am trying to enter this predicate with these values and variables." Note that the variable actually changed for N between the first and second calls.
Now, you'll notice that the [] cannot match the second clause, only the first. The first doesn't have a body but it does establish a binding. So the next line of the trace will reflect that:
Exit: (10) count([], 0) ? creep
See the numbers on the side? That's telling you the depth of the call stack. It's convenient to use numbers for traces instead of showing the nesting visually because eventually our call stacks are going to get pretty deep!
Now that we have a value for the variable, it's going to move on to the next expression in the clause we're in:
Call: (10) succ(0, _8866) ? creep
Exit: (10) succ(0, 1) ? creep
Nesting level went up one but was immediately resolved; this is par for the course with built-ins like succ/2. Now let's look at the rest of the trace:
Exit: (9) count([b], 1) ? creep
Call: (9) succ(1, _8636) ? creep
Exit: (9) succ(1, 2) ? creep
Exit: (8) count([a, b], 2) ? creep
So now that we had a binding for the recursive call, we can enter the next step in the parent call, and so on, until the whole call is resolved and we get 2.
Let's see it again, this time with nesting instead of numbers:
[trace] ?- count([a,b],N).
Call: (8) count([a, b], _8636) ? creep
Call: (9) count([b], _8866) ? creep
Call: (10) count([], _8866) ? creep
Exit: (10) count([], 0) ? creep
Call: (10) succ(0, _8866) ? creep
Exit: (10) succ(0, 1) ? creep
Exit: (9) count([b], 1) ? creep
Call: (9) succ(1, _8636) ? creep
Exit: (9) succ(1, 2) ? creep
Exit: (8) count([a, b], 2) ? creep
N = 2.
This should make what's going on in your own trace a little easier to understand.
Prolog works with unification and pattern matching.You are removing the Head and calling the predicate again with tail which keeps removing Head and tries to find matches after each call and at some later time you have an empty list so prolog search your file for a match and at this point it finds a match ins_sort([], []).
At 6th call you have Call:ins_sort([], _12358) where _12358 is a variable and this variable will get the value from ns_sort([], []). which is a fact. It simply means that if you have one empty list in ns_sort and one variable then set that variable also equals to the empty list, variable gets instantiated with anything if you have all other "terms" matching.
Prolog can be easily understood by learning Backtracking and backtracking is an algorithm so prolog self is an algorithm.

Prolog concatenation confusion

Okay so I've got some code which concatenates two lists. There's no problem with the code at all, rather, just me struggling to understand how it works.
I understand this will be trivial to the most of you.
Here is the code:
conc([],L,L).
conc([H|L1],L2,[H|L3]):-
conc(L1,L2,L3).
Say we have 2 lists: [3, 4, 2] and [9, 9] that I would like to concatenate.
Step 1:
H = 3
L2 = [9, 9]
L1 = 4,2
Step 2:
L2 = [9, 9]
H = 4
L1 = [2]
Step 3:
L2 = [9, 9]
H = 2
L1 = []
Step 4:
L = [9,9] .... Now why does it not just fail? In my brain [9,9] is not equal to [3, 4, 2]
Step 5:
L2 = L3 = [9, 9]
H = 2
L1 = []
Step 6:
L2 = [9, 9]
H = 4
L1 = [2]
L3 = [2, 9, 9]
Step 7:
L2 = [9, 9]
H = 3
L1 = [4, 2]
L3 = [4, 2, 9, 9] And done
What am I finding so confusing? I must be looking at this wrong, also L2 just stays static throughout the recursive calls so I don't understand what we've achieved from moving the first input to the third?
In your step 4, you are not unifying [3, 4, 2] and [9, 9]; you are unifying [9, 9] with a free variable.
You are correct that the bindings in step 3 are L2 = [9, 9], H = 2, and L1 = []. Note that L3 is unbound! The recursive call is conc(L1, L2, L3), so conc([], [9, 9], L3). This goal will unify with the first clause of conc, introducing the new binding L3 = [9, 9]. This is due to the clause conc([], L, L), which forces L3 to be unified with whatever L is bound to, which in this call is just [9, 9].
Then evaluation proceeds with your step 5 as you describe.
Your Prolog might have a tracer that helps you understand this. In SWI-Prolog tracing a conc goal looks like this (the _Gxxxx are free variables):
?- trace, conc([a,b], [c,d], Xs).
Call: (7) conc([a, b], [c, d], _G2467) ? creep
Call: (8) conc([b], [c, d], _G2590) ? creep
Call: (9) conc([], [c, d], _G2593) ? creep
Exit: (9) conc([], [c, d], [c, d]) ? creep
Exit: (8) conc([b], [c, d], [b, c, d]) ? creep
Exit: (7) conc([a, b], [c, d], [a, b, c, d]) ? creep
Xs = [a, b, c, d].
Note that the third Call is entered with a variable as the third argument and Exits with that variable bound to a list.

Declarative interpretation of Bubble Sort algorithm in Prolog

I have some doubt about how work the following implementation of BubbleSort algorithm in Prolog.
I have very clear how BubbleSort algorithm work so my doubt is only related to the Prolog first order logic and declarative interpretation.
This is my code:
bubbleSort(List, SortedList) :- swap(List, List1),
!,
bubbleSort(List1, SortedList).
bubbleSort(SortedList, SortedList).
/* swap predicate swap the two element if X come after Y in lexicographical order */
swap([X,Y|Tail], [Y,X|Tail]) :- X #> Y.
/* If it is not true that X come after Y in lexicographical order let the head in its
original position and call the swap predicate on the sublist */
swap([Z|Tail], [Z|Tail1]) :- swap(Tail, Tail1).
So I have some doubt about declarative interpretation of this program:
The core of the program is the bubbleSort/2 predicate that perform the scansion of List in which are performed eventual swap of adjacent elements.
So swap/2 predicate work like a procedural if:
1) IF the first element in the sublist (X) follow the second element in the sublist (Y) in the lexicographical order it is not good, so perform the swap.
2) Otherwise X and Y are in the right order and try to execute te swap on the sublist Tail
So for example if I perform the following query:
[trace] ?- bubbleSort([3,2,1], Sorted).
I obtain:
Call: (7) bubbleSort([3, 2, 1], _G399) ? creep
Call: (8) swap([3, 2, 1], _G478) ? creep
Call: (9) 3#>2 ? creep
Exit: (9) 3#>2 ? creep
Exit: (8) swap([3, 2, 1], [2, 3, 1]) ? creep
Call: (8) bubbleSort([2, 3, 1], _G399) ? creep
Here call the first version of bubbleSort/2 on the original list. To be TRUE call the first version of swap/2 predicate that check if the first two elements of the list are not in lexicographical order each other, this fact is TRUE so swap these elements. Now that swap/2 predicate is verified, come back by backtracking and (to satisfy bubbleSort/2) call again bubbleSort2 saying now that the original list to order is the list after the swap (the list in which the first and the second element are swapped)
So call again bubbleSort and happen:
Call: (8) bubbleSort([2, 3, 1], _G399) ? creep
Call: (9) swap([2, 3, 1], _G484) ? creep
Call: (10) 2#>3 ? creep
Fail: (10) 2#>3 ? creep
Redo: (9) swap([2, 3, 1], _G484) ? creep
Call: (10) swap([3, 1], _G480) ? creep
Call: (11) 3#>1 ? creep
Exit: (11) 3#>1 ? creep
Exit: (10) swap([3, 1], [1, 3]) ? creep
Exit: (9) swap([2, 3, 1], [2, 1, 3]) ? creep
So now it try to satisfy swap/2 on the list [2,3,1] but now this is not true that the first two elements are not in lexicographical order each other, so the first version of swap/2 predicate fail, so it use the second version that simply call swap/2 on the sublist **[3,1] where it is true that 3 don't follow 1, so swap it and I obtain the [1,3] list.
Here I have the first doubt: after this swap on this sublist (from [3,1] to [1,3]) I obtain the list: [2, 1, 3] in this line:
Exit: (9) swap([2, 3, 1], [2, 1, 3]) ? creep
why? is this because after have satisfied swap(Tail, Tail1) predicate:
swap([Z|Tail], [Z|Tail1]) :- swap(Tail, Tail1).
I have that Tail1 is [1,3] so Z was the old head 2 and [Z|Tail1] is [2,1,3] ?
However, now it is end the first scansion of the bubble sort algorithm that have take the "bigger" element at the end of the list
The execution continues in the same way but I have a doubt about the end of the program execution that is:
Redo: (10) bubbleSort([1, 2, 3], _G399) ? creep
Exit: (10) bubbleSort([1, 2, 3], [1, 2, 3]) ? creep
Exit: (9) bubbleSort([2, 1, 3], [1, 2, 3]) ? creep
Exit: (8) bubbleSort([2, 3, 1], [1, 2, 3]) ? creep
Exit: (7) bubbleSort([3, 2, 1], [1, 2, 3]) ? creep
Sorted = [1, 2, 3].
So at the end call the bubbleSort/2 predicate on an ordered List by:
Redo: (10) bubbleSort([1, 2, 3], _G399) ? creep
where [1,2,3] is List1.
Now I am in the second version of bubbleSort/2 predicate, this one:
bubbleSort(SortedList, SortedList).
where the list to sort and the sorted list is the same (so it means that the original list is totatly sorted).
So what is this? the base case that, once verified, say that the program execution have to end?
So execute backtracking to say that all the previous bubbleSort/2 are verified, untill reach the first bubbleSort/2 call and say that this is vierified and unify Sorted with [1,2,3] list
Is my reasoning correct? Are there a more declarative interpretation of this?
I think the key to understand this algorithm it's the fact that swap/2 fails when the list is sorted - there is no match on empty list.
From here the necessity of the second bubbleSort/2 rule.

Resources