Search code examples
prologtraceunification

Prolog - Explain trace steps in English


plays(alice, leadguitar).
plays(noah, drums).
plays(mike, leadguitar).
plays(mike, drums).
plays(katie, baseguitar).
plays(drew, leadguitar).
plays(drew, baseguitar).

duetwith(Person1,Person2):- 
    plays(Person1,L),
    plays(Person2,L), 
    Person1 \= Person2.

I write a new rule called combo that determines whether or not three people can make a combo with a drummer, a base guitar, and a lead guitar.

combo(Person1,Person2,Person3):- 
    plays(Person1,X), 
    plays(Person2,Y), 
    plays(Person3,Z), 
    X \= Y, 
    Y \= Z, 
    X\=Z.

Can drew, alice, and mike make a combo?

combo(mike,alice,drew).

true so the answer is yes they can make a combo.

I need help understanding the steps the program takes to answer the above query in prolog. Any help with a list of steps is greatly appreciated so I can gain a deeper understanding of each step Prolog takes.

Here is an example of a list of steps Prolog takes for a different example just to give an idea of what I am looking for.

In the case of talkswith(bob,allen), the engine took the following steps:

  1. In

    talkswith(Person1,Person2):- speaks(Person1,L), speaks(Person2,L), Person1 \= Person2.

replace every occurrence of Person1 with bob and Person2 with allen to get

talkswith(bob,allen) :- 
    speaks(bob,L),
    speaks(allen,L),
    bob \= allen.
  1. Let’s see if we can find a value for L that makes the right side true. Start with speaks(bob,L). What can L be? Here are our facts:

    1. speaks(allen, russian).
    2. speaks(bob, english).
    3. speaks(mary, russian).
    4. speaks(mary, english).
  2. In fact 1, the first slot isn’t bob, so that won’t work. In fact 2, the first slot is bob, so let’s try L = englishin speaks(allen,L), bob \= allen. Now we are asking speaks(allen,english), bob\= allen.

  3. Back to the knowledge base. Does speaks(allen,english) match a fact? Not fact 1, not fact 2, not fact 3, not fact 4. This fails.
  4. Now we back up to step 2. Fact 2 didn’t work out, so let’s try fact 3. No, that isn’t bob. Fact 4 isn’t bob either. We can’t find a value for L that works, so the search failed.

Solution

  • replace every occurrence of Person1 with bob

    Prolog uses syntactic unification which never does replacement. A free variable can only be bound once to a value. If it looks like a variable is being changed more than once, it is not. There can be many stack frames and for each stack frame a new set of variables can be created.


    Trace is somewhat helpful but I need to translate that into the form of the example above with the steps written out and explained in plain English.

    For these facts

    plays(alice, leadguitar).
    plays(noah, drums).
    plays(mike, leadguitar).
    plays(mike, drums).
    plays(katie, baseguitar).
    plays(drew, leadguitar).
    plays(drew, baseguitar).
    

    and this predicate

    combo(Person1,Person2,Person3):- 
        plays(Person1,X), 
        plays(Person2,Y), 
        plays(Person3,Z), 
        X \= Y, 
        Y \= Z, 
        X\=Z.
    

    and this query

    combo(mike,alice,drew).
    

    First read SWI-Prolog Debugging and Tracing Programs

    Run the query with trace, unify port enabled and leashing off.

    ?- visible(+unify).
    true.
    
    ?- leash(-all).
    true.
    
    ?- trace.
    true.
    
    [trace] ?- combo(mike,alice,drew).
       Call: (8) combo(mike, alice, drew)
       Unify: (8) combo(mike, alice, drew)
       Call: (9) plays(mike, _7040)
       Unify: (9) plays(mike, leadguitar)
       Exit: (9) plays(mike, leadguitar)
       Call: (9) plays(alice, _7040)
       Unify: (9) plays(alice, leadguitar)
       Exit: (9) plays(alice, leadguitar)
       Call: (9) plays(drew, _7040)
       Unify: (9) plays(drew, leadguitar)
       Exit: (9) plays(drew, leadguitar)
       Call: (9) leadguitar\=leadguitar
       Fail: (9) leadguitar\=leadguitar
       Redo: (9) plays(drew, _7040)
       Unify: (9) plays(drew, baseguitar)
       Exit: (9) plays(drew, baseguitar)
       Call: (9) leadguitar\=leadguitar
       Fail: (9) leadguitar\=leadguitar
       Redo: (9) plays(mike, _7040)
       Unify: (9) plays(mike, drums)
       Exit: (9) plays(mike, drums)
       Call: (9) plays(alice, _7040)
       Unify: (9) plays(alice, leadguitar)
       Exit: (9) plays(alice, leadguitar)
       Call: (9) plays(drew, _7040)
       Unify: (9) plays(drew, leadguitar)
       Exit: (9) plays(drew, leadguitar)
       Call: (9) drums\=leadguitar
       Exit: (9) drums\=leadguitar
       Call: (9) leadguitar\=leadguitar
       Fail: (9) leadguitar\=leadguitar
       Redo: (9) plays(drew, _7040)
       Unify: (9) plays(drew, baseguitar)
       Exit: (9) plays(drew, baseguitar)
       Call: (9) drums\=leadguitar
       Exit: (9) drums\=leadguitar
       Call: (9) leadguitar\=baseguitar
       Exit: (9) leadguitar\=baseguitar
       Call: (9) drums\=baseguitar
       Exit: (9) drums\=baseguitar
       Exit: (8) combo(mike, alice, drew)
    true.
    

    Call: (8) combo(mike, alice, drew)
    

    Prolog looks for a predicate that matches the query. When Prolog looks for a predicate it searches by predicate name, in this case combo, and arity, in this case 3 and finds only one predicate with one clause. Prolog also searches for predicates in the order they appear in the source code. There is more to it (indexing) but that level of detail is not needed to explain this simple query.


    Unify: (8) combo(mike, alice, drew)
    

    Once Prolog has found a clause/fact based on predicate name and arity it checks that the query and head of the clause or fact can be unified.

    mike unifies with Person1. Person1 is now bound to mike.
    alice unifies with Person2. Person2 is now bound to alice.
    drew unifies with Person3. Person3 is now bound to drew.


    Call: (9) plays(mike, _7040)
    

    If the previous statement unifies, then the next statement is called. Each statement is a query in itself. So running the query plays(mike,X). as a stand alone query is the same as this statement in the clause. There are many facts for plays/2 and two of them match plays(mike,X). Prolog uses the first one it finds, however since there are more than one, a choice point is made. We will call this specific choice point plays(mike,X) - cp1 and refer back a specific choice point when a corresponding REDO is encountered.


    Unify: (9) plays(mike, leadguitar)
    

    Once Prolog has found a clause/fact based on predicate name and arity it checks that the query and head of the clause can be unified.

    plays unifies with plays
    mike unifies with mike
    _7040 unifies with leadguitar. _7040 is bound to leadguitar.


    Exit: (9) plays(mike, leadguitar)
    

    This is just completing the ports of the Prolog box model. It shows the result of Call: (9) plays(mike, _7040). No unification takes place for this statement.


    Call: (9) plays(alice, _7040)
    Unify: (9) plays(alice, leadguitar)
    Exit: (9) plays(alice, leadguitar)
    

    Same pattern for alice. Alice has only one fact so not choice points generated for her.


    Call: (9) plays(drew, _7040)
    Unify: (9) plays(drew, leadguitar)
    Exit: (9) plays(drew, leadguitar)
    

    Same pattern for drew. Since drew has two facts for plays(drew,X). a choice point is generated. plays(drew,X) - cp1


    Call: (9) leadguitar\=leadguitar
    

    This is the fourth statement X \= Y with X is bound to leadguitar and Y is bound to leadguitar


    Fail: (9) leadguitar\=leadguitar
    

    Since leadguitar is not different from leadguitar this query fails. Upon failure Prolog goes back to the last choice point (Redo) and tries to find another solution.


    Redo: (9) plays(drew, _7040)
    

    Remember the last choice point created, plays(drew,X) - cp1. Since something failed the query is tried with another possible solution. Since the first choice point for plays(drew,X) failed with X being leadguitar, the second fact is used, plays(drew,baseguitar).


    Unify: (9) plays(drew, baseguitar)
    

    Just shows that the second fact for plays(drew,X) is being used.


    Exit: (9) plays(drew, baseguitar)
    

    It shows the result of Redo: (9) plays(drew, _7040).


    The remainder is just more copy/paste of what has been done already and changing out the statements, variables, bound values, etc.


    This answer is similar, but has more detail.


    Of note is this question

    I write a new rule called combo that determines whether or not three people can make a combo with a drummer, a base guitar, and a lead guitar.

    is not answered by this query

    combo(Person1,Person2,Person3):- 
        plays(Person1,X), 
        plays(Person2,Y), 
        plays(Person3,Z), 
        X \= Y, 
        Y \= Z, 
        X\=Z.
    

    because if these facts are added

    plays(alice, flute).
    plays(noah, cello).
    plays(mike, trumpet).
    

    this query

    ?- combo(alice,noah,mike).
    true ;
    true .
    

    says true but alice, noah and mike are not playing the leadguitar, drums, and baseguitar.