First, a note on terminology: One characteristic of a function is that each input is related to exactly one output. On the other hand, a relation can hold for multiple such combinations. Prolog predicates induce relations between their arguments, and multiple answers can arise on backtracking.
As to your concrete question, @lurker has already given some solid advice.
However, as you already notice, tracing quickly gets very complex in Prolog, and I would therefore like to complement what lurker has written by explaining a few principles of declarative debugging in Prolog.
In essence, we will aim to make our problem simpler instead of harder. We start with the case you mention for the program you posted:
?- transpose([[1,2,3],[1,2,3],[1,2,3]], Ls).
false.
In logical terms, the relation is too specific: It fails to hold in a case in which it ought to hold.
To find a reason for this problem, we could step through the precise execution details, using a textual or graphical tracer, and try to keep track of all the details. This is extremely hard, so I suggest you first try to find a simpler case that still fails.
I will do this by generalizing the query. There are several ways to do this. For example, instead of concrete integers, I can use fresh variables:
?- transpose([[_,_,_],[_,_,_],[_,_,_]], Ls).
false.
Aha, so we have found a much more general case that still fails. As long as this case fails, the more specific case will definitely also fails, because we are reasoning over a pure and monotonic logic program.
So, the new question is: Why does this more general query fail? To find a reason, we can simply further generalize the query. For example, instead of reasoning about lists with 3 elements each, we can generalize this to the following case, where two of the lists are generalized to "more than one element":
?- transpose([[_|_],[_|_],[_,_,_]], Ls).
false.
We see that this much more general query still fails. We can even generalize it further:
?- transpose([_,_,[_,_,_]], Ls).
false.
This still fails! Note that we can also take it too far. For example:
?- transpose([_,_,_], Ls).
nontermination
Here, we run into a different issue altogether, related to termination properties of the program. However, in our case, we want to find a reason for the unexpected failure, and so we return to the case above:
?- transpose([_,_,[_,_,_]], Ls).
false.
At this point, there is not much more to generalize, so we could now invoke the tracer on this simplified query.
However, we can also continue in a different direction: Not every test case needs to be a generalization. We can also come up with an entirely different case, such as a list with only two (instead of three) elements:
?- transpose([_,[_,_,_]], Ls).
false
Aha, so this also fails, even though it ought to succeed!
What about even simpler cases:
?- transpose([[_,_,_]], Ls).
false.
And further:
?- transpose([[_,_]], Ls).
false.
And even further:
?- transpose([[_]], Ls).
false.
Each of these queries ought to succeed, but fails.
Why?
To find reasons, consider program-slicing of the underlying logic program. For example, let us take one of the simplest queries we found that should succeed, but doesn't: ?- transpose([[_]], _).
I use the following definition to generalize away constraints (= goals) of the original program
:- op(950,fy, *).
*_.
For example, we can generalize row/3
as follows:
row([], [], []).
row([H|Tt], [[H|T1]|T], [T1|Z]) :-
* row(Tt,T,Z).
Even with this much more general version of row/3
, we get:
?- transpose([[_]], Ls).
false.
This means: To make this query succeed, you either have to add additional clauses to your code (to make it more general) or change the remaining fragment.
Like lurker, I also leave the rest as an exercise.