Programming Languages: Chapter 14: Logic Programming



Outline


What types of logics are there?

  • propositional logic, a proposition logical statement which is either true or false (e.g., Socrates is a man).
  • predicate logic (also called quantified logic) (e.g., Man(Socrates))


Logic programming

  • re-curring theme: mismatch between formal systems and computer systems
  • logic programming is based on formal logic
  • ``formal logic was developed to provide a method for describing propositions, with the goal of allowing those formally stated propositions to be checked for validity'' [COPL6]
  • ``symbolic logic can be used for the three basic needs of formal logic: to
    • express propositions,
    • express relationships between propositions, and
    • to describe how new propositions can be inferred from other propositions which are assumed to be true'' [COPL6]
  • the form of symbolic logic relevant to logic programming is called first-order predicate calculus



  • essence of logic programming: ``a collection of propositions are assumed to be axioms (i.e., universal truths) and from these axioms, a desired fact is proved by applying the rules of inference in some automated way'' [PLPP].


Propositions

Propositional Logic, Predicate Logic (ref. Randal Nelson and Tom LeBlanc, University of Rochester)
  • atomic propositions:
    • examples:
        man(socrates).
        friend(lucia, sallie).
    • man is called the functor
    • lucia, sallie is the ordered list of parameters
    • when the functor and the ordered list of parameters are written together in the form of a function as one element of a relation, the result is called a compound term
    • no intrinsic semantics (they can mean whatever you want them to mean)
    • man(socrates). can be a fact or query

  • compound propositions: two or more atomic propositions connected by the following logical connectors, or operators:

      Concept Symbol Example Meaning
      negation ¬ ¬anot a
      conjunction aba and b
      disjunction aba or b
      equivalence aba is equivalent to b
      implication ab a implies b
      implication ab b implies a
      entailment |= P |= Q P entails Q
      (precedence proceeds top-down)

      examples:
        abc

        a ∨ ¬bd ≡ (a ∨ (¬b)) ⊃ d

        ab ≡ ¬ ab

  • entailment (semantic consequence):
    • P |= Q
      • read left to right says: `P entails Q'
      • read right to left says: `Q follows from P' or `Q is a semantic consequence of P'
    • means that all of the models (i.e., true rows in the truth table) on the left hand side must also be models on the right hand side
    • for instance, ab |= ab
    • entailment (|=) is not implication (⊃)
    • implication is a statment, entailment is a meta-statement

  • quantifiers (introduce variables):

      QuantifierExampleMeaning
      universalX.P X, P is true
      existential X.P ∃ a value of X such that P is true

    • examples:
        X.(USpresident(X) ⊃ UScitizen(X)).
        X.(USpresident(X) ∧ servedmorethan2terms(X)).
        X.(hascar(lucia, X) ∧ sportscar(X)).

    • scope is attached to atomic proposition
    • use parentheses to indicate scope
    • quantifiers have highest precedence

  • great deal of redundancy in predicate calculus
    • one proposition can be stated several different ways
    • fine for logicians, but poses a problem if we are to implement symbolic logic in a computer system


Clausal form

  • a standard (simplified) form for propositions: B1B2 ∨ ... ∨ BnA1A2 ∧ ... ∧ Am.
    • As and Bs are terms
    • left hand side is consequent
    • right hand side is antecedent
    • interpretation: if all of the A's are true, then at least one of the Bs must be true

  • examples (ref. [COPL6]):
      likes(bob, trout) ⊂ likes(bob, fish) ∧ fish(trout).
      father(louis, al) ∨ father(louis, violet) ⊂ father(al, bob) ∧ mother(violet, bob) ∧ grandfather(louis, bob).

  • advantages
    • existential quantifiers are unnecessary
    • universal quantifiers are implicit in the use of variables in the atomic propositions
    • no operators other than conjunction and disjunction are required
    • all predicate calculus propositions can be converted to clausal form


Horn clauses

  • ``when propositions are used for resolution, only a restricted kind of clausal form called a Horn clause can be used, which further simplifies the resolutions process'' [COPL6]

  • Horn clause: a proposition with 0 or 1 terms in the consequent
    • headless Horn clause: a proposition with 0 terms in the consequent (e.g., {} ⊂ man(jake), or false ⊂ man(jake)), called a goal or query in PROLOG
    • headed Horn clause: a proposition with 1 atomic term in the consequent (e.g.,
      • likes(bob, trout) ⊂ {}. (or likes(bob, trout) ⊂ true.) (called a fact in PROLOG)
      • likes(bob, trout) ⊂ like(bob, fish) ∧ fish(trout). (called a rule in PROLOG))

  • ``most, but not all, propositions can be stated as Horn clauses'' [COPL6] (e.g., p(a) and (∃x, not(p(x))) [PLPP], pp. 565-566)

  • logicPROLOG
    headless Horn clausegoal/query
    headed Horn clausefact/rule


Conversion examples

(ref. [PLPP])


  • basic idea: ``remove or (∨) connectives by writing separate clauses and treat the lack of quantifiers by assuming that variables appearing in the head are universally quantified, while variables appearing in the body (not also in the head) are existentially quantified'' [PLPP]

  • greatest common divisor:
    • specification of GCD (proposition):
        the gcd of u and 0 is u
        the gcd of u and v, if v is not 0, is the same as the gcd of v and the remainder of dividing v into u
    • FOPL:
        u, gcd(u, 0, u).
        u, ∀ v, ∀ w, gcd(u, v, w) ⊂ ∼ zero(v) ∧ gcd(v, u mod v, w).
    • Horn clauses:
        gcd(u, 0, u).
        gcd(u, v, w) ⊂ ¬ zero(v) ∧ gcd(v, u mod v, w).

  • grandparent:
    • specification of grandparent (proposition):
        x is a grandparent of y if x is the parent of someone who is the parent of y
    • FOPL:
        x, ∀ y, (∃ z, grandparent(x,y) ⊂ parent(x,z) ∧ parent(z,y)).
    • Horn clause:
        grandparent(x,y) ⊂ parent(x,z) ∧ parent(z,y).

  • remember, universal quantifier is implicit and existential quantifier is not required:
    all variables on lhs of ⊂ are universally quantified and those on rhs (which do not appear on the lhs) are existentially quantified

  • mammal:
    • specification of mammal (proposition):
        x, if x is a mammal, then x has two or four legs
    • FOPL:
        x, legs(x,2) ∨ legs(x,4) ⊂ mammal(x).
    • Horn clauses:
        legs(x,2) ⊂ mammal(x) ∧ ¬ legs(x,4).
        legs(x,4) ⊂ mammal(x) ∧ ¬ legs(x,2).

  • ``in general the more connectives which appear on the lhs of the ⊂, the harder to translate into a set of Horn clauses'' [PLPP]
  • skolemization: a technique to eliminate existential quantifiers (ref. Thoralf Skolem) involving Skolem constants and functions


Use of all of this: proving theorems (P |= ?)

  • what can we infer from known axioms and theorems?
  • need a deductive apparatus known as rules of inference


Resolution

  • there are many rules of inference in formal systems
  • resolution (ref. Alan Robinson) is the primary rule of inference used in logic programming
  • resolution is a rule of inference which allows new propositions to be inferred from given propositions
  • resolution was devised to be used with propositions in clausal form

  •   (a ⊃ b) ∧ (b ⊃ c)

            a ⊃ c

  • how do we apply resolution? (example)
      ba.
      cb.

      bcab.
      ca.

  • or more generally (assume bi matches a):

                      aa1 ∧ ... ∧ an
                      bb1 ∧ ... ∧ bm
      bb1 ∧ ... ∧ bi-1 a1 ∧ ... ∧ anbi+1 ∧ ... ∧ bm

  • example (ref. [COPL6]):

      older(joanne, jake) ⊂ mother(joanne, jake).
      wiser(joanne, jake) ⊂ older(joanne, jake).
      older(joanne, jake) ∧ wiser(joanne, jake) ⊂ mother(joanne, jake) ∧ older(joanne, jake).
      wiser(joanne, jake) ⊂ mother(joanne, jake).

  • example (ref. [COPL6]):

      father(bob, jake) ∨ mother(bob, jake) ⊂ parent(bob, jake).
      grandfather(bob, fred) ⊂ father(bob, jake) ∧ father(jake, fred).

      (father(bob, jake) ∨ mother(bob, jake)) ∧ grandfather(bob, fred) ⊂ parent(bob, jake) ∧ father(bob, jake) ∧ father(jake, fred).

      mother(bob, jake) ∨ grandfather(bob, fred) ⊂ parent(bob, jake) ∧ father(jake, fred).

  • backward chaining systems (PROLOG) vs. forward chaining systems (CLIPS)

  • hypothesis vs. goal

  • fact vs. goal
      mammal(human) ⊂ {}. (a fact)
      {} ⊂ mammal(human). (a goal)
      {} ⊂ mammal(human) ∧ legs(x,2). (a goal; each term on rhs is called a sub-goal)

  • proof by contradiction:
    goodday(thu).
    
    ?- goodday(thu).
    
    goodday(thu) ⊂ true.
    false ⊂ goodday(thu).

    false ⊂ true (a contradiction)

  • resolution algorithm ([PLPP]):

      goal: {} ⊂ a
      rule: aa1 ∧ ... ∧ an

      match goal with head of one of the known clauses, and replace the matched goal with the body of the clause, creating a new list of sub-goals

      therefore, the original goal is replaced with subgoals: {} ⊂ a1 ∧ ... ∧ an

      if, after multiple iterations of this process, we end up with the empty Horn clause {} ⊂ {}, then the proposition has been proved (i.e., it is a theorem)

  • resolution can be slow on a large database

  • the presence of variables in propositions makes the process of resolution more complex than this
    • requires temporary assignment of values to variables called instantiation
    • the process of determining useful values for variables is called unification
    • unification often involves backtracking

  • logic programming = resolution + unification


Resolution examples

  • example (ref. [PLPP]):

      mammal(human) ⊂ {}.

      {} ⊂ mammal(human).

      mammal(human) ⊂ mammal(human).

      {} ⊂ {} (proved!)

  • another example (ref. [PLPP]):

      legs(x,2) ⊂ mammal(x) ∧ arms(x, 2).
      legs(x,4) ⊂ mammal(x) ∧ arms(x, 0).
      mammal(horse) ⊂ {}.
      arms(horse,0) ⊂ {}.

      {} ⊂ legs(horse, 4).

      legs(x,4) ⊂ mammal(x) ∧ arms(x,0) ∧ legs(horse, 4). (using the second rule above)

      now to cancel out, we need unification (bind x to horse)

      legs(horse,4) ⊂ mammal(x) ∧ arms(x,0) ∧ legs(horse, 4).
      {} ⊂ mammal(x) ∧ arms(x,0).

      mammal(horse) ⊂ mammal(horse) ∧ arms(horse,0). (using the third rule above)
      {} ⊂ arms(horse,0).

      arms(horse,0) ⊂ arms(horse,0). (using the fourth rule above)
      {} ⊂ {}. (proved!)


Logic programming

  • tries to make programming a specification (of solution) activity (called declarative programming), but it falls shorts
  • PROLOG (PROgramming LOGic) is a declarative programming language


Formalism gone awry

  • what is the problem with implementing resolution in a computer system?
  • how should be search the database: top-down, bottom-up, or neither?
  • the goal {} ⊂ legs(horse, 4) led to 2 sub-goals: {} ⊂ mammal(x) ∧ arms(x,0)
  • in which order should the system try to prove the sub-goals? left-to-right or right-to-left or neither?
  • in this case, the end result (true) is the same, but in other cases in might make a difference (e.g. (see proof tree on p. 559 of [PLPP]),
      ancestor(a,z) ⊂ parent(a,b) ∧ ancestor(b,z).
      ancestor(a,b) ⊂ parent(a,b).
      parent(olimpia,lucia). parent(lucia,liesel).

      {} ⊂ ancestor(a,liesel).
    )
  • therefore: order in which
    • the database, and
    • sub-goals are searched
    is significant

  • an implementation must use a fixed search strategy, and the programmer must be aware of these (unsettling)
  • questions
    • how to search the database? top-down or bottom-up or neither?
    • how to search the sub-goals? left-to-right or right-to-left or neither?
  • PROLOG searches its database top-down and searches sub-goals left-to-right (i.e., using depth-first-search)
  • do not confuse backward chaining with bottom-up search
  • this violates the defining principle of a declarative language, that is that the programmer need worry only about the logic and leave the control (inference methods used to prove an answer) up to the system (resolution comes for free with PROLOG, programmer need not program it!)
  • we have a pure functional programming language (Haskell), can we have a pure logic programming language?
  • the answer is yes, but it would be far too inefficient to be practical
  • holy grail of logic programming: original goal of logic programming was to make programming a specification activity (i.e., declarative programming!)
  • pure logic programming is non-deterministic; programmers should not have to impart control flow
  • therefore, PROLOG falls short


Summary

If we have a goal Q, to prove Q, PROLOG must either
  • find Q as a fact in the database, or
  • find Q as a sequence of propositions such that
      P2P1.
      P3P2.
      ...
      QPn.


Applications of logic programming

  • logical reasoning
  • problem solving
    • cryparithmetic problems (e.g.,
      SEND
      +MORE
      ------------
      MONEY
      )
    • GRE analytical problems
    • puzzles
  • artificial intelligence
  • specification verification (or rapid prototyping)
  • equational logic programming languages (e.g., OBJ3 or Equation Interpreter Project)


Essential PROLOG programming

  • PROLOG programs are built from terms; a term is either a constant, variable, or structure
  • assert facts and rules
    • constants and predicates must start with a lower case letter
    • have no intrinsic semantics; they are whatever you want them to mean
  • ask questions; variables must start with a CAPITAL LETTER or _
  • simple rules; head and body of rule
  • in PROLOG, all functions are predicates (i.e., return true or false); pass additional arguments to simulate returns of other types
  • use period (.), not semicolon (;)
  • two ways of consulting a database (i.e., compiling a PROLOG program):
    • use the consult predicate (i.e., consult('filename')., or
    • use [filename].)
    • ?- consult('movies.pl').
      ?- [movies]. % abbreviated form of the above
          
  • use make. to re-consult a file (in SWI-PROLOG)
  • use n or ; character to get next solution
  • comments
    • % introduces a comment until the end of a line
    • C style comments /* ... */ are also permitted, but unlike in C, in PROLOG these comments can be nested
  • halt. or EOF character (e.g., ctrl-D on UNIX, ends session with PROLOG)
  • use predicate protocol/1 to log your session with PROLOG (e.g., protocol('diary').)
  • backtracking
  • tracing and trace/0: allows user to trace the resolution process, including instantiations, as PROLOG tries to satisfy a goal
  • outputting text
    • write
    • writeln
    • nl (newline)
  • call/1 is the PROLOG analog of eval in Scheme
  • add the following goal
    set_prolog_flag(toplevel_print_options,[quoted(true), portray(true),
    max_depth(0)]).
    
    to a program to prevent PROLOG from abbreviating results with ellipses. Here the value of max depth indicates how deep into the list it will print. By default it is 10; if it is set to 0, then the printing depth limit is turned off.

  • Kowalski's classic formulation of PROLOG strategy: algorithm = logic + control
  • contrast with Wirth's formulation of imperative programming: programs = algorithms + data structures


Unification examples

    (ref. [PLPP])
    me = me.
    
    me = you.
    
    me = X.
    
    f(a,X) = g(Y,b).
    
    f(a,X) = f(Y,b).
    
    f(X) = g(X).
    
    gcd(U,0,U).
    gcd(U,V,W) :- V \=0, R is U mod V, gcd(V,R,W).
    


Lists in PROLOG

  • uses brackets (like ML and Haskell, but not LISP) to specify lists
  • list construction/decomposition in PROLOG vs. Haskell
    -----------------------
    PROLOG          Haskell
    -----------------------
    [X|Y]           X:Y
    .(X,Y)          X:Y
    [X,Y]           X:Y:nil
    [X]             X:nil
    [X,Y|Z]         X:Y:Z
    [X,Y,Z|W]       X:Y:Z:W
    -----------------------
    
  • the following expressions are nonsense
    X|Y             
    [X|Y,Z]         
    [X|Y|Z]     
    


Simple PROLOG database

    animal(cow).
    animal(dog).
    animal(ramsay).
    animal(rogerrabbit).
    animal(yak).
    animal('Emu').
    
    likes(lucia,lucy).
    likes(lucy,apples).
    likes(lucia,lucia).
    
    cpscourse(cps352).
    cpscourse(cps543).
    cpscourse(cps430).
    wtcourse(wt150).
    wtcourse(wt151).
    challenging(_x) :- cpscourse(_x).
    easy(X) :- wtcourse(X).
    easy(cps352).
    
    ihave([pencil,pen,watch]).
    ihave([cps352,cps430,cps444,cps350]).
    ihave([itall]).
    ihave([[toyota,2006,corolla],[2008,honda,civic]]).
    ihave([book,[pen1, pen2],[newdollabill]]).
    ihave(itall).
    


Simple session with PROLOG

    ?- consult(first).
    % animal compiled 0.00 sec, 936 bytes
    
    Yes
    ?- animal(X).
    
    X = cow 
    
    Yes
    ?-  animal(X).
    
    X = cow ;
    
    X = dog ;
    
    X = ramsay ;
    
    X = rogerrabbit ;
    
    X = yak ;
    
    X = emu ;
    
    No
    ?- animal(WhatIwant).
    
    WhatIwant = cow ;
    
    WhatIwant = dog ;
    
    WhatIwant = ramsay ;
    
    WhatIwant = rogerrabbit ;
    
    WhatIwant = yak ;
    
    WhatIwant = emu ;
    
    No
    ?- animal(whatiwant).
    
    No
    ?- animal(whatIwant).
    
    No
    ?- animal(X), animal(Y).
    
    X = cow
    Y = cow ;
    
    X = cow
    Y = dog ;
    
    X = cow
    Y = ramsay ;
    
    X = cow
    Y = rogerrabbit ;
    
    X = cow
    Y = yak ;
    
    X = cow
    Y = emu ;
    
    X = dog
    Y = cow ;
    
    X = dog
    Y = dog ;
    
    X = dog
    Y = ramsay ;
    
    X = dog
    Y = rogerrabbit ;
    
    X = dog
    Y = yak ;
    
    X = dog
    Y = emu ;
    
    X = ramsay 
    Y = cow ;
    
    X = ramsay
    Y = dog ;
    
    X = ramsay
    Y = ramsay ;
    
    X = ramsay 
    Y = rogerrabbit ;
    
    X = ramsay
    Y = yak ;
    
    X = ramsay
    Y = emu ;
    
    X = rogerrabbit
    Y = cow ;
    
    X = rogerrabbit
    Y = dog ;
    
    X = rogerrabbit
    Y = ramsay ;
    
    X = rogerrabbit
    Y = rogerrabbit ;
    
    X = rogerrabbit
    Y = yak ;
    
    X = rogerrabbit
    Y = emu ;
    
    X = yak
    Y = cow ;
    
    X = yak
    Y = dog ;
    
    X = yak
    Y = ramsay ;
    
    X = yak
    Y = rogerrabbit ;
    
    X = yak
    Y = yak ;
    
    X = yak
    Y = emu ;
    
    X = emu
    Y = cow ;
    
    X = emu
    Y = dog ;
    
    X = emu
    Y = ramsay ;
    
    X = emu
    Y = rogerrabbit ;
    
    X = emu
    Y = yak ;
    
    X = emu
    Y = emu ;
    
    No
    ?- animal(X), animal(Y), X\=Y.
    
    X = cow
    Y = dog ;
    
    X = cow
    Y = rabbit ;
    
    X = cow
    Y = rogerrabbit 
    
    ?- likes(lucia,X), likes(X,apples).
    
    X = lucy ;
    
    ?- cpscourse(X).
    
    X = cps534 ;
    
    X = cps430 ;
    
    X = cps352 ;
    
    No
    ?- easy(X).
    
    X = cps352 ;
    
    X = wt150 ;
    
    X = wt151 ;
    
    No
    
    ?- ihave(X).
    
    X = [pencil,pen,watch] ;
    
    X = [cps352,cps430,cps444,cps350] ;
    
    X = [itall] ;
    
    X = [[toyota,1998,corolla],[1997,honda,civic]] ;
    
    X = [book,[pen1,pen2],[newdollabill]] ;
    
    X = itall ;
    
    No
    ?- ihave([X|Y]).
    
    X = pencil
    Y = [pen,watch] ;
    
    X = cps352
    Y = [cps430,cps444,cps350] ;
    
    X = itall
    Y = [] ;
    
    X = [toyota,1998,corolla]
    Y = [[1997,honda,civic]] ;
    
    X = book
    Y = [[pen1,pen2],[newdollabill]] ;
    
    No
    ?- ihave([X,Y|Z]).
    
    X = pencil
    Y = pen
    Z = [watch] ;
    
    X = cps352
    Y = cps430
    Z = [cps444,cps350] ;
    
    X = [toyota,1998,corolla]
    Y = [1997,honda,civic]
    Z = [] ;
    
    X = book
    Y = [pen1,pen2]
    Z = [[newdollabill]] ;
    
    No
    ?- ihave([X,Y]).
    
    X = [toyota,1998,corolla]
    Y = [1997,honda,civic] ;
    
    No
    ?- ihave([X]).
    
    X = itall ;
    
    No
    ?- ihave([X,Y,Z]).
    
    X = pencil
    Y = pen
    Z = watch ;
    
    X = book
    Y = [pen1,pen2]
    Z = [newdollabill] ;
    
    No
    ?- halt.
    


Simple control

  • ancestor
    • order of clauses matter
    • left recursion is bad, since PROLOG uses DFS!
  • mutual recursion is bad: doesn't cause stack overflow, but infinite loop nevertheless
  • backtracking to find alternate solutions
  • /* in ancestor(A,Z), A is meant to be the ancestor of Z */
    ancestor(A,Z) :- parent(A,B), ancestor(B,Z).
    ancestor(A,B) :- parent(A,B).
    parent(olimpia,lucia).
    parent(lucia,liesel).
    
    %?- ancestor(X,liesel).
    
    % simple re-write
    /*
    ancestor(A,Z) :- parent(Y,Z), ancestor(A,Y).
    ancestor(A,B) :- parent(A,B).
     */
    
    % now using left-recursion, just a flip of the first above
    /*
    ancestor(A,Z) :- ancestor(B,Z), parent(A,B).
     */
    
    % still using left-recursion, but now a flip of the second above
    /*
    ancestor(A,Z) :- ancestor(A,Y), parent(Y,Z).
     */
    
    /* left recursion is bad */
    /* why doesn't left recursion cause a problem in reverse?  */
    
    
    %classletoffearly(onwed).
    
    /*
    niceday(X) :- classletoffearly(X).
    classletoffearly(X) :- niceday(X).
    niceday(mon).
     */
    
    %protocol('transcript').
    
    analyze search tree:



    (uses graph syntax from [PLPP] Fig. 12.2, p. 559)


List codes written in class

    isempty([]).
    
    islist([]).
    /* only one of the following is required, the first is preferred */
    islist([_|_]).
    islist([_|T]) :- islist(T).
    
    /* only one of the following is required, the first is preferred */
    cons(H,T,[H,T]).
    cons(H,T,L) :- L = [H|T].
    
    /* member is built-in */
    member(E,[E|_]).
    member(E,[_|T]) :- member(E,T).
    


Using append as a primitive to construct some simple list predicates

    /* predicate to append two lists; */
    /* first two are the inputs, last is the appended list */
    
    /* this predicate has a minor bug; */
    /* it does not prune duplicates (see below) */
    /* fix it */
    append([],L,L).
    append(L,[],L).
    append([H|T],Y,[H|W]) :- append(T,Y,W).
    
    /* predicate to check if X is a member of list List */
    member(E,L) :- append(_,[E|_],L).
    
    
    
    /* predicate to check if X is a sublist of Y */
    sublist(X,Y) :- append(_,X,W), append(W,_,Y).
    
    /* predicate to triple a list
       given [3] produce [3,3,3] as answer
       L when tripled gives list LLL */
    triple(L,LLL) :- append(L,L,LL), append(LL,L,LLL).
    
    /* predicate to reverse a list
       give X as input, Y is the reversed X */
    reverse([],[]).
    /* why isn't left recursion a problem here? */
    reverse([H|T],RL) :- reverse(T,RT), append(RT,[H],RL).
    
    /* predicate to do bubblesort
       X when bubblesorted gives Y */
    bubblesort(L,SL) :- append(M,[A,B|N],L),
                        A > B,
                        append(M,[B,A|N],S),
                        %bubblesort(S,SL).
                        bubblesort(S,SL), !.
    bubblesort(L,L).
    /* fix this code yourself, so as to
       not give more answers after the correct one */
    


Paths

    /* edge(X,Y) means there is a directed edge from X to Y */
    
    edge(a,b).
    edge(b,c).
    edge(c,a).
    
    /* path(X,Y) is true when there is a
    directed path from X to Y */
    
    /* have a third argument that keeps a running tally of nodes visited so far */
    
    path(X,X,_).
    path(X,Y,T)  :- edge(X,Z),
                    notamember(Z,T),
                    append([Z,T,T2),
                    path(Z,Y,T2).
    /* we can go from X to Y through Z only
    if Z was not already visited in T */
    
    notamember(E,[]).
    notamember(E,[H|T]) :- E \= H, notamember(E,T).
    


Analogs to relational databases

(primarily for CPS 430/542)


    % relation, called predicate in PROLOG
    movies1(roger_rabbit,1990,123,paramount).
    movies1(get_shorty,1966,122,fox).
    
    movies2(get_shorty,1966,122,fox).
    
    % union
    moviesU(X,Y,Z,W) :- movies1(X,Y,Z,W).
    moviesU(X,Y,Z,W) :- movies2(X,Y,Z,W).
    
    % intersection
    moviesI(X,Y,Z,W) :- movies1(X,Y,Z,W), movies2(X,Y,Z,W).
    
    % difference
    moviesD(X,Y,Z,W) :- movies1(X,Y,Z,W), not(movies2(X,Y,Z,W)).
    
    % projection
    title(T) :- movies1(T,_,_,_).
    
    % selection
    new_movies(T,Y,L,fox) :- movies1(T,Y,L,fox), L >= 121.
    
    % selection followed by a projection
    new_movies2(T) :- movies1(T,_,L,fox), L >= 121.
    
    % or
    new_movies2(T) :- movies1(T,_,L,S), S = 'fox', L >= 121.
    
    % theta-join
    fox_stars(T,Y,L,fox,N) :- movies(T,Y,L,fox), actors(N,T,fox).
    
    % natural join
    movies_aug(T,Y,L,S,N) :- movies(T,Y,L,S), stars(N,T,S).
    


Imparting more control in PROLOG: the cut operator (!)

  • ! is called cut; it is the goto of PROLOG; use it with great precaution
  • cut (!) always evaluates to true (e.g.,
    ?- !.
    Yes
    
    )
  • fail always evaluates to false (e.g.,
    ?- false.
    No
    
    )
  • uses of cut
    • preventing consideration of alternate solutions
    • preventing multiple solutions from being produced
    • reduce number of branches in search tree to pursue
    • 'freezing' parts of solutions
    all really one in the same

  • example
    /* cut prevents consideration of alternate
       solutions by freezing parts of current solution.  */
    juice(apple).
    juice(grape).
    juice(orange).
    
    /* execute the above code with the following goals:
    ?- juice(X).
    ?- juice(X), juice(Y).
    ?- juice(X), !, juice(Y) */
    
    juice(apple).
    juice(grape) :- !.
    juice(orange).
    


More cut examples

  • example (ref. [PLPP] pp. 561-562):
    ancestor(A,Z) :- parent(A,B), ancestor(B,Z), !.
    ancestor(A,B) :- parent(A,B).
    parent(olimpia,lucia).
    parent(lucia,liesel).
    




    (uses graph syntax from [PLPP] Fig. 12.4, p. 561)
    ancestor(A,Z) :- writeln('a'), !, parent(A,B), ancestor(B,Z).
    ancestor(A,B) :- writeln('b'), parent(A,B).
    parent(olimpia,lucia) :- writeln('c').
    parent(lucia,liesel) :- writeln('d').
    




    (uses graph syntax from [PLPP] Fig. 12.5, p. 562)
    ancestor(A,Z) :- !, parent(A,B), ancestor(B,Z).
    ancestor(A,B) :- parent(A,B).
    parent(olimpia,lucia).
    parent(lucia,liesel).
    
    ancestor(A,Z) :- parent(A,B), ancestor(B,Z).
    ancestor(A,B) :- !, parent(A,B).
    parent(olimpia,lucia).
    parent(lucia,liesel).
    
  • member example:
    /* cut here prevent member from finding all occurrences */
    member(E,[E|_]) :- !.
    member(E,[_|T]) :- member(E,T).
    
    member(E,[1,2,1,7]).
    E = 1 ;
    
    No
    
  • squarelist example:
    /* squarelist takes a list and squares every integer element of the list;
       if an element is not an integer,
       it is simply inserts the element as is into the result list, the second argument
     
       here, cut is serving two purposes:
          - fix rule 2 when you know A is an integer 
          - skip all subsequent rules (in this case, rule 3) */
    squarelist([],[]).
    squarelist([A|B],[C|D]) :- integer(A), !,
                               C is A*A,
                               squarelist(B,D).
    squarelist([A|B],[A|D]) :- squarelist(B,D).
    
  • practice with cuts from [PPFC]
    /* more practice with cuts;
       determine what each of back1, back2, and back3 will print */
    back1 :- example(X),
             d(Y),
             write(X), write(Y), nl,
             fail.
    
    back2 :- example(X), !,
             d(Y),
             write(X), write(Y), nl,
             fail.
    
    back3 :- example(X),
             e(Y),
             write(X), write(Y), nl,
             fail.
    
    example(1).
    example(2).
    
    d(1).
    d(2).
    d(3).
    
    e(1) :- !.
    e(2).
    


Towers of Hanoi in PROLOG

    /* move n disks from peg A to peg B using peg C as intermediary;
       requires (2^n)-1 moves, where n is the number of discs */
    
    /* does this cut serve any useful purpose? */
    move(0,_,_,_) :- !.
    move(N,A,B,C) :- M is N-1,
                     move(M,A,C,B),
                     step(A,B),
                     move(M,C,B,A).
    
    step(A,B) :- write('Move one disc from peg '),
                 write(A),
                 write(' to peg '),
                 write(B),
                 writeln('.'),
    
    /* Towers of Hanoi is an exponential-time algorithm.
       Orders of Growth: if we ran an exponential-time algorithm with a
       data set of size n=100 on a computer which performed
       1 billion operations per second, we would have to
       wait for approximately 4 x 10^11 centuries for the
       code to finish running!  */
    


Math in PROLOG

    gcd(U,0,U).
    gcd(U,V,W) :- V \=0, R is U mod V, gcd(V,R,W).
    
    factorial(0,1).
    factorial(X,Y) :- X > 0,
                      Z is X-1,
                      factorial(Z,M),
                      Y is M*X.
    
    sum(0,0).
    sum(X,Y) :- X > 0,
                Z is X-1,
                sum(Z,M),
                Y is M+X.
    


Negation in PROLOG

  • is the not/1 predicate in PROLOG a logical NOT?
  • exercise care when using it
  • its use can produce counter-intuitive results
  • mother(mary).
    
    ?- mother(X).
    X = mary
    
    true.
    
    ?- not(mother(X)).
    false % this is not saying 'there are no mothers' 
    
    ?- not(not(mother(X))).
    X = _G157
    
    starts with the innermost clause and succeeds with X = mary,
    the moment the clause becomes false, the instantiation is released
    
    ?- not(not(mother(mary))).
    true.
    
    ?- not(X=0) % returns no, without binding X 
    false.
    
    /* in logic the order should not matter */
    ?- X=0, not(X=1). % instantiates X to 0 */
    ?- not(X=1), X=0. % fail at first sub-goal without binding X to 0. why?
    
    ?- not(not(X=1)), X=0.
    X=0.
    
    ?- not(X=1), not(X=0).
    false.
    
    moral of the story: not in PROLOG is not a logical NOT


Reflective predicates

Recall, PROLOG programs are built from terms. A term is either a constant, variable, or structure.
  • assert/1 or assertz/1: adds a fact to the end of the database
  • asserta/1: adds a fact to the beginning of the database
  • retract/1: removes a fact from the database
  • % indicates that car is a dynamic predicate
    :- dynamic car/1.
    
    car(honda).
    car(toyota).
    
    ?- car(CAR).
    
    CAR = honda ;
    
    CAR = toyota ;
    
    No
    ?- asserta(car(ford)).
    
    Yes
    ?- assertz(car(bmw)).
    
    Yes
    ?- retract(car(honda)).
    
    Yes
    ?- car(CAR).
    
    CAR = ford ;
    
    CAR = toyota ;
    
    CAR = bmw ;
    
    No
    ?- 
    
  • assert and retract can be used to build a tic-tac-toe program
  • clause/2: matches the head and body of an existing clause in the database; can be used to implement a metacircular interpreter (i.e., an implementation of call/1 [PLP] p. 578)
  • var(Term): succeeds if Term is currently a free variable
  • nonvar(Term): succeeds if Term is currently not a free variable
  • ground(Term): succeeds if Term holds no free variable


Impurities: mismatch between LOGIC and PROLOG

    FOPLLP (PROLOG)
    any form of proposition possible restricted to Horn clauses
    order in which sub-goals are searched insignificant order in which sub-goals are searched significant (l-to-r)
    order in which clauses are searched insignificant order in which clauses are searched significant (top-down)
    logical NOT NOT as failure (due to unification)

    in summary, ``there are aspects of predicate calculus that PROLOG cannot capture and there are aspects of PROLOG (e.g., its imperative and database manipulating features) that have no analogues in predicate calculus'' [PLP1] p. 642

    another impure feature of PROLOG: closed world assumption; PROLOG is a true/fail system, like our legal system, rather than a true/false system [COPL]


Problems with PROLOG

(these are all somewhat interrelated)
  • to be purely declarative, programmer should not be required to affect control flow for program success
    • consequent of DFS search strategy
    • left recursion often leads to incorrect results

  • closed world assumption (and nonmonotonic reasoning); PROLOG is a true-fail system, not a true-false system [COPL] p. 643. There is no mechanism in PROLOG by which to assert facts assumed to be false. PROLOG relies on pure positive logic. This is another reason why not in PROLOG (see below) is not a logical not. As a result not(P) can succeed simply because PROLOG cannot prove P true.

  • Horn clauses are not expressive enough to capture all knowledge in first-order predicate calculus (e.g., propositions in clausal form with a disjunction of more than one nonnegated term such as every `every positive natural number is either even or odd'
    even(N) ∨ odd(N) ⊂ natural(N). 
    
    )
  • negation as failure (a reflection of PROLOG's limitation to Horn clauses)
    odd(N) :- natural(N), not(even(N)).
    even(N) :- natural(N), not(odd(N)).
    
  • more negation issues: not(transmission(X,manual)) means ¬ ∃X (transmission(X,manual)) or `there are no cars with manual transmissions' rather than ∃X (¬transmission(X,manual)) or `not all cars have a manual transmission' (example inspired by [PLP2] p. 582). As a result, the goal not(transmission(X,manual)) fails even if we have the fact transmission(accord,manual) in our database.

  • the occurs-check problem


Semantic part of PROLOG interpreter

(an implementation of the built-in call/1) (ref. [EBG=PE])
    prolog(Leaf) :- clause(Leaf,true).
    prolog((Goal1, Goal2)) :- prolog(Goal1), prolog(Goal2).
    prolog(Goal) :- clause(Goal,Clause), prolog(Clause).
    


References

    [EBG=PE] F. van Harmelen and A. Bundy. Explanation-Based Generalisation = Partial Evaluation. Artificial Intelligence, 36(3), 401-412, 1988.
    [COPL6] R.W. Sebesta. Concepts of Programming Languages. Addison-Wesley, Boston, MA, Sixth edition, 2003.
    [FCDB] J.D. Ullman and J. Widom. A First Course in Database Systems. Prentice Hall, Upper Saddle River, NJ, Second edition, 2002.
    [PLP1] M.L. Scott. Programming Language Pragmatics. Morgan Kaufmann, San Francisco, First edition, 2000.
    [PLP2] M.L. Scott. Programming Language Pragmatics. Morgan Kaufmann, Amsterdam, Second edition, 2006.
    [PLPP] K.C. Louden. Programming Languages: Principles and Practice. Brooks/Cole, Pacific Grove, CA, Second edition, 2002.
    [PPFC] P. Burna. Prolog Programming A First Course.

Return Home