Question

I have a very simple min function written in Prolog, and I don't understand how it works.

Code:

min(E, [E]) :- write('case 1: '), write(E), nl.
min(E, [E|L]) :- write('case 2: '), write(E), write(' '), write([E|L]), nl, min(F, L), E =< F.
min(E, [F|L]) :- write('case 3: '), write(E), write(' '), write([F|L]), nl, min(E, L), E =< F.

We've just started using Prolog in class, and I don't understand how it evaluates recursive cases like this one. I included print statements in this function to see what's going on, and I don't understand some of the steps here:

10 ?- min(E, [2, 1]).
case 2: 2 [2,1]
case 1: 1
case 2: 1 [1]
case 3: _L164 [1]
case 3: _G323 [2,1]
case 1: 1
E = 1 .

I understand the first two calls, but I don't understand what happens after the line case 1: 1. Why does it call on line 3 the second case min(E, [E|L]) after going to case 1, min(E, [E])? That doesn't follow from anywhere in the code. If someone could explain what's going on after those first two calls, that would be great. I've looked around for some explanations, but I haven't been able to understand what's going on here.

Was it helpful?

Solution

To figure this out, we'll play prolog interpreter. :)

min(E, [E]) :-
    write('case 1: '), write(E), nl.
min(E, [E|L]) :-
    write('case 2: '), write(E), write(' '), write([E|L]), nl,
    min(F, L),
    E =< F.
min(E, [F|L]) :-
    write('case 3: '), write(E), write(' '), write([F|L]), nl,
    min(E, L),
    E =< F.

We do the query:

min(E, [2,1]).

(A) Prolog starts from the first clause, min(E, [E]) and fails since [2,1] can't unify with [E]. It then goes to the next clause, min(E, [E|L]), and is able to unify [2,1] with [E|L] by unifying E with 2 and L with [1] and then we see:

case 2: 2 [2,1]    % This is E instantiated as 2, and [E|L] as [2|[1]]

(B) Prolog then makes the recursive query, min(F, [1]). From here, it's back to the top of the clause list (on a new query, it starts from the top) and is able to unify the variables in the first clause, min(E, [E]) by unifying F with 1. We then see:

case 1: 1

(C) This query succeeds and comes back to the clause it was queried from and encounters E =< F where E is unified with 2 and F is unified with 1. But then E =< F will fail since 1 =< 2 is not true. At this point, Prolog will backtrack and re-attempt the prior recursive query it just did, min(F, [1]). Recall that the query had already exercised the first clause and succeeded so now on backtrack it will attempt the second clause. It looks to unify min(F, [1]) with min(E, [E|L]) and can do so by unifying E with 1 and L with []. Then clause 2 executes and we get:

case 2: 1 [1]

(D) We're now an additional call deep in clause 2. We haven't finished the first one yet. So this new call will query min(F, []) (remember L is unified with [] in this case). There are no clauses in your predicate that match min(F, []), so it fails. So this instance of the case 2 query fails completely (backtracks through the writes which don't re-execute on backtrack). This was the recursive query from (C) above.

(E) Since case 2 failed on the recursive call from (C), Prolog continues to backtrack and reattempts by executing the third clause and unifies min(E, [F|L]) with min(F, [1]) (note: these are "different" F's) by unifying the first F with 1, the L with [] and E is unified with the second F (but is uninstantiated - does not have a value assigned). It's important to note here that in Prolog two variables can be unified but not yet be assigned a value. Since the head of the third clause has been unified, case 3 executes and we see:

case 3: _L164 [1]    % This is E (uninstantiated) and [F|L] ([1|[]])

The _L164 appears because we are writing an uninstantiated variable. Uninstantiated variables, in output like this, appear as a generated variable name preceded by an underscore (_).

(F) So case 3 executes and does a recursive call to min(E, L) where E is uninstantiated and L is []. This query will fail because there are no clauses that match min(_, []). Prolog will then backtrack from case 3 and then the entire recursive call from (C) to min(F, [1]) has failed.

(G) Remember that we got to (F) from a recursive call in case 2 described in (C). Since that recursive call has failed (as described in (D) through (F)) Prolog resumes the case 2 described in (C) by backtracking, failing case 2, and moving on to case 3. This whole execution of the predicate was from the original query, min(E, [2,1]). The head of the third clause is min(E, [F|L]) and Prolog unifies the first E with the second E (uninstantiated, however), unifies F with 2 and L with [1]. We now see:

case 3: _G323 [2,1]   % This is E (uninstantiated) and [F|L] ([2|[1]])

(H) Case 3 proceeds and does a recursive query on min(E, [1]) (having instantiated L with [1]) which starts from the top again, matches the first clause min(E, [E]) and prolog unifies E with 1 and matches the head of the clause.. We then see:

case 1: 1

(I) Case 1 succeeds, and comes back to case 3 which proceeds and checks E =< F which is 1 =< 2 (see unifications in (G)) which is true. We've now completely succeeded case 3!

And we're done! With the success of case 3 (case 1 having failed as described in (A), and case 2 having failed as described in (E)), the original query has succeeded by unifying E with 1 and we see:

E = 1.

When you do a query in Prolog, it will start with the first clause of the predicate you are querying and attempt each clause, in sequence, until it finds one that is successful and then it will declare success. If they all fail, then, of course, the query fails. In the process of attempting each clause, if there is a recursive query (call), that recursive call will start from the first clause again. Each recursive call is its own full-blown query on the predicate. Thus, each recursive call will start back at the first clause of the predicate, taking it's own journey of truth-seeking through each of the predicate's clauses. This is an important principle to know about Prolog which will help understand basic recursive behavior.

On the topic of tracing, the write statements in the code do a good job of showing what clauses of the predicate fire. But they don't show what queries within the clauses fail, which is just as important to know when trying to understand what's happening in the queries. So it can be a little confusing with just the write statements. The gtrace (or trace) command, suggested by @User, would show succeeding and failing queries. It's a good tool to use to see what's happening in the clauses, perhaps in conjunction with the write statements to peek at variables and such.

OTHER TIPS

You can use gtrace in SWI-Prolog to trace evaluation.

10 ?- gtrace, min(E, [2, 1]).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top