fullscreen
timer
qrcode
plickers
selector
edit
reset

Prolog

COS 382 - Language Structures

Prolog

Introduction to Prolog

logic programming → prolog

Logic programming languages are not procedural or functional

class Student extends Person { int year; float gpa; string major; };

can be represented, among others, as

/* student(_), person(_), inYear(_,_), hasGpa(_,_), inMajor(_,_), ... */
person(X) :- student(X).
student(jane).             /* jane = new Student(); */
inYear(jane, 3).           /* jane.year  = 3; */
hasGpa(jane, 3.94).        /* jane.gpa   = 3.94; */
inMajor(jane, 'cs').       /* jane.major = "cs"; */

Separate logic from control:

[ http://www.cs.rutgers.edu/~lou/314-f04-slides/ ]

prolog

Created in 1970s

Roots in first-order logic

Declarative: program logic is expressed in terms of relations, facts and rules

Name is abbreviation for programmation en logique (French for programming in logic)

Useful for rule-based logic queries (db searching), voice control systems, template filling, theorem proving, expert systems, natural language processing

[ en.wikipedia.org/wiki/Prolog ]

prolog

Programs composed of:

facts

Example of facts:

likes(eve, pie).
likes(al,  eve).
likes(eve, tom).
likes(eve, eve).

food(pie).
food(apple).
person(tom).

Constants: eve, tom, ...

Predicates: likes(...), person(...), ...

queries (asking questions)

/**** simpler queries ****/

likes(al, eve).
likes(al, pie).
likes(eve, al).
likes(person, food).

likes(al, Who).                      /* Who is variable */
likes(eve, Who).
likes(Who, eve).

/**** harder queries ****/

likes(A, B).
likes(D, D).
likes(eve, W).
likes(eve, W), person(W).            /* , means AND */
likes(al, V), likes(eve, V).

/**** more hard queries ****/

likes(eve, W), likes(W, V).          /* W has same binding */
likes(eve, W), person(W), food(V).
likes(eve, V), (person(V); food(V)). /* ; means OR */
likes(eve, W), \+ likes(al, W).      /* \+ (mostly) means NOT */
[ powered by Tau Prolog, test on SWISH ]

rules

Facts:

likes(eve, pie).  likes(al, eve).  likes(eve, tom).  likes(eve, eve).
food(pie).  food(apple).  person(tom).

What if you want to ask the same question often? Add a rule to the database.

rule1 :- likes(eve, V), person(V).

Query:

?- rule1.
true.

rules

Facts and Rules:

likes(eve, pie).  likes(al, eve).  likes(eve, tom).  likes(eve, eve).
food(pie).  food(apple).  person(tom).

rule1     :- likes(eve, V), person(V).
rule2(V)  :- likes(eve, V), person(V).

Query:

?- rule2(H).
H = tom ;
false.

Note: rule1 and rule2 are just like any other predicate!

family tree example

Facts:

male(paul).
male(dan).
male(jon).
male(samuel).
male(linus).
male(tobin).

female(linda).
female(cheryl).
female(cindy).
female(cora).
female(felicity).
female(ada).
female(edith).
female(luna).
female(twig).
female(thunder).

mother(linda,cindy).
mother(cheryl,jon).
mother(cindy,cora).
mother(cindy,samuel).
mother(cindy,linus).
mother(cindy,felicity).
mother(cindy,ada).
mother(cindy,edith).
mother(cindy,tobin).

father(paul,cindy).
father(dan,jon).
father(jon,cora).
father(jon,samuel).
father(jon,linus).
father(jon,felicity).
father(jon,ada).
father(jon,edith).
father(jon,tobin).

pet(luna).
pet(twig).
pet(thunder).

Reminder: the atoms above are meaningless to Prolog; only has meaning to us.

family tree example

Rules:

parent(M,C) :- mother(M,C).
parent(F,C) :- father(F,C).
parents(M,F,C) :- mother(M,C), father(F,C).

/* implied */
grandparent(X,Z) :- parent(X,Y), parent(Y,Z).
/* Y is unified to all possible values that make */
/* RHS true, but Y is not reported */

What about the following?

sister_of(X,Y) :- female(X), parents(M,F,X), parents(M,F,Y).

We'll come back to this soon...

horn clauses

In Prolog, a horn clause

\[c \leftarrow h_1, h_2, h_3, ..., h_n\]

is written as

c :- h1, h2, ..., hn.

Horn Clause is a Clause
Consequent (c) is a Goal or a Head
Antecedents (h1,h2,...) are Subgoals or Tail

horn clauses

Horn Clause with Head but No Tail is a Fact:

male(jon).


Horn Clause with Head and Tail is a Rule:

father(jon, linus) :- male(jon), parents(M, jon, linus).


Horn Clause with No Head but Tail is executed right away:

:- male(M), writeln(M).

horn clauses

Variables may appear in the antecedents and consequent of a Horn Clause

c(X1, ..., Xn) :- h(X1, ..., Xn, Y1, ..., Yk).

For all values of X1, ..., Xn, the formula c(X1, ..., Xn) is true if there exist values of Y1, ..., Yk such that the formula h(X1, ..., Xn, Y1, ..., Yk) is true.

Call Y1, ..., Yk auxiliary variables. Their values will be bound to make consequent true, but they are not reported by Prolog, because they don't appear in the consequent.

declarative semantics

Prolog program consists of facts and rules.

Rules like

sister_of(X,Y) :- female(X), parents(M,F,X), parents(M,F,Y).
/* X is the sister of Y, if X is female and there are M and F who */
/* are X's parents as well as Y's parents. */

correspond to logical formulas

\[\begin{eqnarray} \forall X,Y . \mathit{sister\_of}(X,Y) \leftarrow \exists M,F\ .\ & ( & \\ & & \mathit{female}(X)\ \wedge \\ & & \wedge\ \mathit{parents}(M,F,X)\ \wedge \\ & & \wedge\ \mathit{parents}(M,F,Y) \\ & ) & \end{eqnarray}\]

Note: variables not in head are existentially quantified

declarative semantics

A query is a conjunction of atoms to be proven


Prolog system will attempt to use resolution to find a value for X such that the query is true.

?- sister_of(X, samuel).

family tree example

Queries:

sister_of(X, samuel).
sister_of(X, Y).     /* what happens here? */
sister_of(X, edith). /* or here? */

We'll come back to this example soon...

[ powered by Tau Prolog ]

pattern matching

Prolog uses pattern matching on text of facts and rules

Can apply with

pattern matching

Forward chaining

pattern matching

Backward chaining

a simple example

Facts/Rules a:-b. b:-c. c:-d. b.
Query ?- a.


Goals: a

Prolog begins by searching for any rule that makes a true

  • Prolog adds a to its list of goals to prove

Goals: ba

The first rule states that a is true if b is true

  • Prolog adds the goal b to its list.

Goals: cba

The second rule states that b is true if c is true

  • c is a goal.

Goals: dcba

The third rule states that c is true if d is true

  • d is a goal.

Goals: dcba

Prolog can't prove d with given facts and rules (fails)

  • Prolog removes the goal d from its list
  • Tries to prove c in a different way
  • This is known as backtracking.

Goals: cba

Given the facts, Prolog can't prove c either

  • Prolog backtracks again and tries to prove b another way.

Goals: ba

Using the last line of the program b., Prolog proves b, and thus a, and then terminates.

?- a.
true.
src ]

cut operator

Backtracking
if first subgoal is satisfied, but second is not, reconsider first subgoal for alternate solution that might allow second subgoal to succeed


We can control Prolog's backtracking behavior by using the cut operator (!).

cut operator

Here is an example of using the cut operator. (SWISH)

Prolog searches for all values of Q and R that satisfy a(Q,R), which is every combination of c(X) (2 facts) and d(Y) (3 facts).

What will Prolog respond with when queried b(Q,R)?

Once Prolog finds a X such that c(X) is true, it crosses the cut (!) and therefore commits to that X while trying to prove b(X,Y). After the cut, Prolog finds all Y that make d(Y) true, and then it stops searching as it cannot backtrack across cut.

src ]

cut operator

A subtle note is that a cut operator prevents Prolog from backtracking, but only for the current clause.

A small tweak to the previous program illustrates this.

a(X,Y) :- c(X),    d(Y).
b(X,Y) :- c(X), !, d(Y).
e(X,Y) :- c(X),  b(X,Y).

c(1). c(2).
d(1). d(2). d(3).

Querying e(Q,R) will cause Prolog to find all X such that C(X) is true, then Prolog tries to prove b(X,Y). The b(X,Y) goal tries to prove c(X) (already true for X!), crosses the cut, then proves d(Y). Once all Y have been found, Prolog cannot backtrack across the cut, so the b(X,Y) goal is finished, and Prolog goes back to proving e(X,Y) using other X.

src ]

cut operator

Prolog Program

src, powered by Tau Prolog ]

family tree example revisited

Now, let's go back to the sister_of rule

edith is a sister of edith? Oops!

[ powered by Tau Prolog ]

family tree example revisited

Prolog searches using backtracking

sister_of(X,Y) :- female(X), parents(M,F,X), parents(M,F,Y).

?- sister_of(X, edith).
  1. Find X that makes female(X) true.
  2. Find M and F to make parents(M,F,X) true for that X.
  3. Find Y to make parents(M,F,Y) true for those M and F.

This algorithm is recursive; each find works on a new "copy" of the facts+rules. Eventually, each find must be resolved by appealing to facts.

The process is called backward chaining.

Variables are local: every time rule is used, new names for X,Y,M,F.

family tree example revisited

Rule ordering (from first to last) used in search

Unification requires all instances of the same variable in a rule to get the same value

Unification does not require differently named variables to get different values: sister_of(edith, edith)

All rules searched if requested by successive typing of ;


With all this, how do we fix sister_of?

family tree example revisited

Need to modify sister_of to be

sis_of(X,Y) :- female(X), parents(M,F,X), parents(M,F,Y), \+(X==Y).
/* last subgoal disallows X,Y to have same value */

?- sis_of(X, edith).
X = cora ;
X = felicity ;
X = ada.

Note:

family tree example revisited

[ powered by Tau Prolog ]

negation as failure

\+(P) succeeds when P fails

Called negation by failure and defined as

not(X) :- X, !, fail.
not(_).

which means:

mnemonic: + refers to provable and the backslash (\) is normally used to indicate negation in Prolog

Prolog

Digging Deeper into Prolog Details

from logic to prolog

Suppose we have the following predicate proposition that we wish to express in Prolog

\[\forall A \biggl[ \neg s(A) \rightarrow \Bigl( \neg d(A) \land \neg \exists B \bigl[ t(A,B) \land c(B) \bigr]\Bigr)\biggr]\]

where

\[s : \text{student} \quad d : \text{dorm resident} \quad t : \text{takes} \quad c : \text{class}\]


Convert first order predicate proposition to clausal form and then to Prolog syntax

from logic to prolog

\[\forall A \biggl[ \neg s(A) \rightarrow \Bigl( \neg d(A) \land \neg \exists B \bigl[t(A,B) \land c(B)\bigr]\Bigr)\biggr]\]

Eliminate implication and equivalence

\[\forall A \biggl[ s(A) \lor \Bigl(\neg d(A) \land \neg \exists B \bigl[ t(A,B) \land c(B)\bigr]\Bigr)\biggr]\]

from logic to prolog

\[\forall A \biggl[ s(A) \lor \Bigl(\neg d(A) \land \neg \exists B \bigl[ t(A,B) \land c(B)\bigr]\Bigr)\biggr]\]

Move negation inward so only negated items are individual terms

\[\forall A\biggl[s(A) \lor \Bigl(\neg d(A) \land \forall B\bigl[\neg\bigl(t(A,B) \land c(B)\bigr)\bigr]\Bigr)\biggr]\]

\[\forall A\biggl[s(A) \lor \Bigl(\neg d(A) \land \forall B\bigl[\neg t(A,B) \lor \neg c(B)\bigr]\Bigr)\biggr]\]

from logic to prolog

\[\forall A\biggl[s(A) \lor \Bigl(\neg d(A) \land \forall B\bigl[\neg t(A,B) \lor \neg c(B)\bigr]\Bigr)\biggr]\]

Move quantifiers to the outside and adopt convention that all variables are universally quantified

\[s(A) \lor \Bigl(\neg d(A) \land \bigl(\neg t(A,B) \lor \neg c(B)\bigr)\Bigr)\]

Use distributive, associative, and commutative properties to put in conjunctive normal form, where \(\land\) and \(\lor\) operators are nested no more than two deep with \(\land\) on the outside and \(\lor\) on the inside.

\[\bigl[s(A) \lor \neg d(A)\bigr] \, \land \, \bigl[s(A) \lor \neg t(A,B) \lor \neg c(B)\bigr]\]

conjunctive normal form

from logic to prolog

Finally, convert each logical clause to Prolog fact or rule

Within each clause, move negated terms to right and non-negated terms to left (already done for this example)

\[\bigl[s(A) \lor \neg d(A)\bigr] \, \land \, \bigl[s(A) \lor \neg t(A,B) \lor \neg c(B)\bigr]\]

Recast disjunctions as implications

\[(s(A) \leftarrow \neg(\neg d(A))) \land (s(A) \leftarrow \neg(\neg t(A,B) \lor \neg c(B)))\] \[(s(A) \leftarrow d(A)) \land (s(A) \leftarrow (t(A,B) \land c(B)))\]

Translate to Prolog

student(A) :- dormResident(A).
student(A) :- takes(A,B), class(B).

prolog syntax

Prolog has four terms

See ANTLR grammar on GitHub

prolog syntax

Atoms are one of the following

Some atoms have special meaning


Numbers are usually integers, but can be floats (1, -2, 3.141)

prolog syntax

Variables are defined as follows

Notes:


Example facts

likes(al, eve). likes(eve, eve).

prolog syntax

Complex terms

prolog program

Running a program consists of asking the interpreter a question

Questions asked by stating a theorem (assert a predicate)

The interpreter tries to prove true or false

Prolog prints the values of variables that make predicate true (except variables starting with underscore)

prolog program

Programs are stored in files and consulted (kind of like importing)

?- consult('familytree.pl').
% familytree.pl compiled 0.00 sec, 19 clauses
true.

?- ['familytree.pl'].           /* alternative approach   */
?- ['file1.pl', 'file2.pl'].    /* can consult many files */

Notice that consult is a functor and 'familytree.pl' is an atom!

prolog program

You can dynamically add new facts and rules to the database using assert, remove with retract

?- assert( father(jon) ).
true.
?- assert( male(X) :- father(X) ).
true.
?- male(X).
X = jon.

Note: the double parens are necessary for asserts with rules containing conjunctions

?- assert( father_of(jon, dan) ).       /* true. */
?- assert( father_of(linus, jon) ).     /* true. */

?- assert( grandfather_of(X,Z) :- father_of(X,Y), father_of(Y,Z) ).
ERROR: assert/2: Uninstantiated argument expected, found
father_of(_G4266,_G4267) (2-nd argument)

?- assert(( grandfather_of(X,Z) :- father_of(X,Y), father_of(Y,Z) )).
true.

lists

Lists are a series of terms separated by commas and enclosed in square brackets

[]
[the, giraffe, dreams]
[_, X, Y]
['sarlac pit', father_of(luke,'Nooo'), [11.99,parsecs], shotfirst(han)]

Lists can be written as

[Head | TailList]
[First, Second | TailList]
[First, Second | [Third, Fourth | TailList]]

For example

?- assert([1,2,3]).     /* true.                        */
?- [X|Y].               /* X = 1, Y = [2,3].            */
?- [X,Y|Z].             /* X = 1, Y = 2, Z = [3].       */
?- [X,Y,Z|A].           /* X = 1, Y = 2, Z = 3, A = []. */

lists: member_of

Let's write a set of rules to prove that an element is a member of a list

?- member_of(chewie, [han,leia,luke,'c-3po','r2-d2',chewie,lando]).
true.

How do we define this? (hint: use recursion!)

Recall a list can be written [H|T], so X is a member of the list if...

lists: member_of

Let's write a set of rules to prove that an element is a member of a list

?- member_of(chewie, [han,leia,luke,'c-3po','r2-d2',chewie,lando]).
true.

How do we define this? (hint: use recursion!)

Recall a list can be written [H|T], so X is a member of the list if...

Base case:

member_of(X, [X|_]).

Inductive step / rule:

member_of(X, [_|Y]) :- member_of(X,Y).

lists: append

Now let's write a set of rules that will append (concat) one list (second) to the end of another (first)

?- append( [1,2], [3,4], [1,2,3,4] ).   /* true. */

We can use recursion again...

lists: append

Now let's write a set of rules that will append (concat) one list (second) to the end of another (first)

?- append( [1,2], [3,4], [1,2,3,4] ).   /* true. */

We can use recursion again...

Base case:

/* appending Y to an empty list results in Y */
append([], Y, Y).

Inductive step / rule:

append([H | X], Y, [H | Z]) :- append(X, Y, Z).

If the list Z is Y appended to X, then Y appended to the list [H|X] (which is one element larger) is [H|Z]

lists: append

factorial

Let's look at the classic recursive problem of computing factorial

\[\begin{eqnarray} \mathit{factorial}(n) & = & \begin{cases} 1 & \text{if } n = 0 \\ n \cdot \textit{factorial}(n - 1) & \text{otherwise} \end{cases} \end{eqnarray}\]

In Prolog

factorial(0, 1).
factorial(N, F) :- N>0, M is N-1, factorial(M, S), F is N*S.


The is term is actually an infix functor (M is N-1 is same as is(M, N-1)) for temporary instantiation ("local variable"). See documentation (link) for more details.

factorial

Now, let's test our factorial rules using Prolog's debug trace

?- assert(factorial(0,1)).
true.
?- assert((factorial(N,F) :- N>0, M is N-1, factorial(M,S), F is N*S)).
true.

?- factorial(3,6).
true.

?- factorial(4,X).
X = 24 ;
false.

?- trace(factorial).
% factorial/2: [call,redo,exit,fail]
true.

[debug]  ?- factorial(4,X).
 T Call: (6) factorial(4, _G1692)
 T Call: (7) factorial(3, _G1800)
 T Call: (8) factorial(2, _G1803)
 T Call: (9) factorial(1, _G1806)
 T Call: (10) factorial(0, _G1809)
 T Exit: (10) factorial(0, 1)
 T Exit: (9) factorial(1, 1)
 T Exit: (8) factorial(2, 2)
 T Exit: (7) factorial(3, 6)
 T Exit: (6) factorial(4, 24)
X = 24 ;
 T Redo: (10) factorial(0, _G1809)
 T Fail: (10) factorial(0, _G1809)
 T Fail: (9) factorial(1, _G1806)
 T Fail: (8) factorial(2, _G1803)
 T Fail: (7) factorial(3, _G1800)
 T Fail: (6) factorial(4, _G1692)
false.

[debug]  ?- nodebug.
true.

?- factorial(4,X).
X = 24 ;
false.

Note: factorial(0, _G1809) redos on line 29, because Prolog has a rule (line 3) that might prove true, but Prolog fails on line 30, because 0>0 is not true.

factorial

Here is a question about Prolog that has an important but subtle answer:


Why does this work

factorial(0,1).
factorial(N,F) :- N>0, M is N-1, factorial(M,S), F is N*S.

?- factorial(3,6).
true.

but this does NOT work?

factorial2(0,1).
factorial2(N,F) :- N>0, factorial2(N-1,S), F is N*S.

?- factorial(3,6).
false.

factorial

Similar to how is is a functor, - is also a functor.

Therefore, N-1 is actually -(N,1) (a complex term), and a complex term does not unify with a number!

?- (1-1) == 0.
false.

In other words, the complex term 3-1-1-1 (which really is -(-(-(3,1),1),1)) does not unify with 0 in the fact factorial2(0,1), so Prolog cannot prove the query.

You must force Prolog to evaluate the - by instantiating / binding a variable with is or using the =:= comparison

?- X is 1-1, X == 0.
X = 0.

?- (1-1) =:= 0.
true.

factorial

factorial(0,1).
factorial(N,F) :- N>0, N1 is N-1, factorial(N1,F1), F is N*F1.

factorial2(0,1).
factorial2(N,F) :- N>0, factorial2(N-1,S), F is N*S.

Here is the trace debug of the two calls

[debug]  ?- factorial(3,X).
 T Call: (6) factorial(3, _G349)
 T Call: (7) factorial(2, _G460)
 T Call: (8) factorial(1, _G463)
 T Call: (9) factorial(0, _G466)
 T Exit: (9) factorial(0, 1)
 T Exit: (8) factorial(1, 1)
 T Exit: (7) factorial(2, 2)
 T Exit: (6) factorial(3, 6)
X = 6 ;
 T Redo: (9) factorial(0, _G466)
 T Fail: (9) factorial(0, _G466)
 T Fail: (8) factorial(1, _G463)
 T Fail: (7) factorial(2, _G460)
 T Fail: (6) factorial(3, _G349)
false.

[debug]  ?- factorial2(3,X).
 T Call: (6) factorial2(3, _G349)
 T Call: (7) factorial2(3-1, _G460)
 T Call: (8) factorial2(3-1-1, _G463)
 T Call: (9) factorial2(3-1-1-1, _G466)
 T Fail: (9) factorial2(3-1-1-1, _G466)
 T Fail: (8) factorial2(3-1-1, _G463)
 T Fail: (7) factorial2(3-1, _G460)
 T Fail: (6) factorial2(3, _G349)
false.

Important Prolog notes!

Two more important notes about Prolog


First, Prolog is not purely declarative, so reordering the actions in the Prolog program changes the path Prolog uses

Searching and backtracking have deterministic imperative semantics

Important Prolog notes!

Second, the negation as failure term (\+) is not equivalent to the logical not (\(\neg\))

src ]

Important Prolog notes!

But, if we instead wrote

What happened?

src ]

Important Prolog notes!

The two are logically equivalent, but Prolog attempts to prove subgoals left-to-right.

enjoys(vincent, X) :- burger(X), \+ big_kahuna_burger(X).
enjoys(vincent, X) :- \+ big_kahuna_burger(X), burger(X).

Using the new rule, Prolog first checks whether
\+ big_kahuna_burger(X) holds, which means that it must check whether big_kahuna_burger(X) fails.

But this succeeds, because the database contains the fact big_kahuna_burger(b).

So \+ big_kahuna_burger(X) fails, and Prolog backtracks to make another attempt.

Since this subgoal is the first and it failed, Prolog concludes that it cannot prove the query to be true.

src ]

Accumulators

Accumulators are commonly implemented pattern used to increase readability, maintainability, and efficiency of recursive rules

For example, we can rewrite the factorial rule

factorial(0,1).
factorial(N,F) :- N>0, N1 is N-1, factorial(N1,F1), F is N*F1.

to use an accumulator

factorial3(0,F,F).
factorial3(N,A,F) :- N>0, A1 is N*A, N1 is N-1, factorial3(N1,A1,F).
factorial3(N,F) :- factorial3(N,1,F).  /* helper "kick-off" rule */

The second parameter is called an accumulating parameter, and it will "accumulate" the solution while it recurses.

The accumulator version uses tail recursion, which can be heavily optimized by the system

src ]

Accumulators

factorial(0,1).
factorial(N,F) :- N>0, N1 is N-1, factorial(N1,F1), F is N*F1.

factorial3(0,F,F).
factorial3(N,A,F) :- N>0, A1 is N*A, N1 is N-1, factorial3(N1,A1,F).
factorial3(N,F) :- factorial3(N,1,F).  /* helper "kick-off" rule */
?- factorial(4,X).
 T [10] Call: factorial(4, _13006)
 T [19] Call: factorial(3, _14186)
 T [28] Call: factorial(2, _15066)
 T [37] Call: factorial(1, _480)
 T [46] Call: factorial(0, _1218)
 T [46] Exit: factorial(0, 1)
 T [37] Exit: factorial(1, 1)
 T [28] Exit: factorial(2, 2)
 T [19] Exit: factorial(3, 6)
 T [10] Exit: factorial(4, 24)
X = 24 ;
 T [10] Redo: factorial(4, 24)
 T [19] Redo: factorial(3, 6)
 T [28] Redo: factorial(2, 2)
 T [37] Redo: factorial(1, 1)
 T [46] Redo: factorial(0, 1)
 T [46] Fail: factorial(0, _1218)
 T [37] Fail: factorial(1, _480)
 T [28] Fail: factorial(2, _330)
 T [19] Fail: factorial(3, _184)
 T [10] Fail: factorial(4, _18)
false.
?- factorial3(4,X).
 T [10] Call: factorial3(4, _6014)
 T [19] Call: factorial3(4, 1, _6014)
 T [28] Call: factorial3(3, 4, _6014)
 T [37] Call: factorial3(2, 12, _6014)
 T [46] Call: factorial3(1, 24, _6014)
 T [55] Call: factorial3(0, 24, _6014)
 T [55] Exit: factorial3(0, 24, 24)
 T [46] Exit: factorial3(1, 24, 24)
 T [37] Exit: factorial3(2, 12, 24)
 T [28] Exit: factorial3(3, 4, 24)
 T [19] Exit: factorial3(4, 1, 24)
 T [10] Exit: factorial3(4, 24)
X = 24 ;
 T [10] Redo: factorial3(4, 24)
 T [19] Redo: factorial3(4, 1, 24)
 T [28] Redo: factorial3(3, 4, 24)
 T [37] Redo: factorial3(2, 12, 24)
 T [46] Redo: factorial3(1, 24, 24)
 T [55] Redo: factorial3(0, 24, 24)
 T [55] Fail: factorial3(0, 24, _18)
 T [46] Fail: factorial3(1, 24, _18)
 T [37] Fail: factorial3(2, 12, _18)
 T [28] Fail: factorial3(3, 4, _18)
 T [19] Fail: factorial3(4, 1, _18)
 T [10] Fail: factorial3(4, _18)
false.

Accumulators

Reversing a list is another example of using an accumulator, where we prepend to the accumulator the head of the list

list: [a,b,c,d]  accumulator: []
list: [b,c,d]    accumulator: [a]
list: [c,d]      accumulator: [b,a]
list: [d]        accumulator: [c,b,a]
list: []         accumulator: [d,c,b,a]
src ]

Accumulators

Naive implementation

/* H: Head, T: Tail, R: list Reversed, RevT: T Reversed */
rev([],[]).
rev([H|T], R) :- rev(T, RevT), append(RevT, [H], R).

Using accumulator

/* A: Accumulator, H,T,R: same as above */
rev2([], A, A).
rev2([H|T], A, R) :- rev2(T, [H|A], R).
rev2(L, R) :- rev2(L, [], R).  % helper "kick-off" rule

Last rule is a helper that initializes the accumulator for us

The naive rev version takes 90 steps to reverse a list of 8 elements, but accumulator rev2 takes about 20.

src ]

Accumulators

/* H: Head, T: Tail, R: list Reversed, RevT: T Reversed */
rev([],[]).
rev([H|T], R) :- rev(T, RevT), append(RevT, [H], R).

rev2([], A, A).
rev2([H|T], A, R) :- rev2(T, [H|A], R).
rev2(L, R) :- rev2(L, [], R).  % helper "kick-off" rule
?- trace(rev).
%         rev/2: [all]
true.
?- rev([1,2,3,4,5,6,7,8], L).
 T [10] Call: rev([1, 2, 3, 4, 5, 6, 7, 8], _30394)
 T [19] Call: rev([2, 3, 4, 5, 6, 7, 8], _31690)
 T [28] Call: rev([3, 4, 5, 6, 7, 8], _382)
 T [37] Call: rev([4, 5, 6, 7, 8], _1256)
 T [46] Call: rev([5, 6, 7, 8], _2136)
 T [55] Call: rev([6, 7, 8], _3016)
 T [64] Call: rev([7, 8], _3896)
 T [73] Call: rev([8], _4776)
 T [82] Call: rev([], _5656)
 T [82] Exit: rev([], [])
 T [73] Exit: rev([8], [8])
 T [64] Exit: rev([7, 8], [8, 7])
 T [55] Exit: rev([6, 7, 8], [8, 7, 6])
 T [46] Exit: rev([5, 6, 7, 8], [8, 7, 6, 5])
 T [37] Exit: rev([4, 5, 6, 7, 8], [8, 7, 6, 5, 4])
 T [28] Exit: rev([3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3])
 T [19] Exit: rev([2, 3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3, 2])
 T [10] Exit: rev([1, 2, 3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3, 2, 1])
L = [8, 7, 6, 5, 4, 3, 2, 1].
?- trace(rev2).
%         rev2/2: [all]
%         rev2/3: [all]
true.
?- rev2([1,2,3,4,5,6,7,8], L).
 T [10] Call: rev2([1, 2, 3, 4, 5, 6, 7, 8], _5818)
 T [19] Call: rev2([1, 2, 3, 4, 5, 6, 7, 8], [], _5818)
 T [28] Call: rev2([2, 3, 4, 5, 6, 7, 8], [1], _5818)
 T [37] Call: rev2([3, 4, 5, 6, 7, 8], [2, 1], _5818)
 T [46] Call: rev2([4, 5, 6, 7, 8], [3, 2, 1], _5818)
 T [55] Call: rev2([5, 6, 7, 8], [4, 3, 2, 1], _5818)
 T [64] Call: rev2([6, 7, 8], [5, 4, 3, 2, 1], _5818)
 T [73] Call: rev2([7, 8], [6, 5, 4, 3, 2, 1], _5818)
 T [82] Call: rev2([8], [7, 6, 5, 4, 3, 2, 1], _5818)
 T [91] Call: rev2([], [8, 7, 6, 5, 4, 3, 2, 1], _5818)
 T [91] Exit: rev2([], [8, 7, 6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1])
 T [82] Exit: rev2([8], [7, 6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1])
 T [73] Exit: rev2([7, 8], [6, 5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1])
 T [64] Exit: rev2([6, 7, 8], [5, 4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1])
 T [55] Exit: rev2([5, 6, 7, 8], [4, 3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1])
 T [46] Exit: rev2([4, 5, 6, 7, 8], [3, 2, 1], [8, 7, 6, 5, 4, 3, 2, 1])
 T [37] Exit: rev2([3, 4, 5, 6, 7, 8], [2, 1], [8, 7, 6, 5, 4, 3, 2, 1])
 T [28] Exit: rev2([2, 3, 4, 5, 6, 7, 8], [1], [8, 7, 6, 5, 4, 3, 2, 1])
 T [19] Exit: rev2([1, 2, 3, 4, 5, 6, 7, 8], [], [8, 7, 6, 5, 4, 3, 2, 1])
 T [10] Exit: rev2([1, 2, 3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3, 2, 1])
L = [8, 7, 6, 5, 4, 3, 2, 1].

seems about the same, except rev uses append...

?- trace(append).
%         lists:append/2: [all]
%         lists:append/3: [all]
%         append/1: [all]
true.
?- rev([1,2,3,4,5,6,7,8], L).
 T [10] Call: rev([1, 2, 3, 4, 5, 6, 7, 8], _18716)
 T [19] Call: rev([2, 3, 4, 5, 6, 7, 8], _20010)
 T [28] Call: rev([3, 4, 5, 6, 7, 8], _20890)
 T [37] Call: rev([4, 5, 6, 7, 8], _21770)
 T [46] Call: rev([5, 6, 7, 8], _22650)
 T [55] Call: rev([6, 7, 8], _23530)
 T [64] Call: rev([7, 8], _24410)
 T [73] Call: rev([8], _25290)
 T [82] Call: rev([], _26170)
 T [82] Exit: rev([], [])
 T [82] Call: lists:append([], [8], _25290)
 T [82] Exit: lists:append([], [8], [8])
 T [73] Exit: rev([8], [8])
 T [73] Call: lists:append([8], [7], _24410)
 T [82] Call: lists:append([], [7], _31002)
 T [82] Exit: lists:append([], [7], [7])
 T [73] Exit: lists:append([8], [7], [8, 7])
 T [64] Exit: rev([7, 8], [8, 7])
 T [64] Call: lists:append([8, 7], [6], _816)
 T [73] Call: lists:append([7], [6], _3464)
 T [82] Call: lists:append([], [6], _4354)
 T [82] Exit: lists:append([], [6], [6])
 T [73] Exit: lists:append([7], [6], [7, 6])
 T [64] Exit: lists:append([8, 7], [6], [8, 7, 6])
 T [55] Exit: rev([6, 7, 8], [8, 7, 6])
 T [55] Call: lists:append([8, 7, 6], [5], _670)
 T [64] Call: lists:append([7, 6], [5], _9028)
 T [73] Call: lists:append([6], [5], _9918)
 T [82] Call: lists:append([], [5], _10808)
 T [82] Exit: lists:append([], [5], [5])
 T [73] Exit: lists:append([6], [5], [6, 5])
 T [64] Exit: lists:append([7, 6], [5], [7, 6, 5])
 T [55] Exit: lists:append([8, 7, 6], [5], [8, 7, 6, 5])
 T [46] Exit: rev([5, 6, 7, 8], [8, 7, 6, 5])
 T [46] Call: lists:append([8, 7, 6, 5], [4], _524)
 T [55] Call: lists:append([7, 6, 5], [4], _16206)
 T [64] Call: lists:append([6, 5], [4], _17096)
 T [73] Call: lists:append([5], [4], _17986)
 T [82] Call: lists:append([], [4], _18876)
 T [82] Exit: lists:append([], [4], [4])
 T [73] Exit: lists:append([5], [4], [5, 4])
 T [64] Exit: lists:append([6, 5], [4], [6, 5, 4])
 T [55] Exit: lists:append([7, 6, 5], [4], [7, 6, 5, 4])
 T [46] Exit: lists:append([8, 7, 6, 5], [4], [8, 7, 6, 5, 4])
 T [37] Exit: rev([4, 5, 6, 7, 8], [8, 7, 6, 5, 4])
 T [37] Call: lists:append([8, 7, 6, 5, 4], [3], _378)
 T [46] Call: lists:append([7, 6, 5, 4], [3], _24998)
 T [55] Call: lists:append([6, 5, 4], [3], _25888)
 T [64] Call: lists:append([5, 4], [3], _26778)
 T [73] Call: lists:append([4], [3], _27668)
 T [82] Call: lists:append([], [3], _28558)
 T [82] Exit: lists:append([], [3], [3])
 T [73] Exit: lists:append([4], [3], [4, 3])
 T [64] Exit: lists:append([5, 4], [3], [5, 4, 3])
 T [55] Exit: lists:append([6, 5, 4], [3], [6, 5, 4, 3])
 T [46] Exit: lists:append([7, 6, 5, 4], [3], [7, 6, 5, 4, 3])
 T [37] Exit: lists:append([8, 7, 6, 5, 4], [3], [8, 7, 6, 5, 4, 3])
 T [28] Exit: rev([3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3])
 T [28] Call: lists:append([8, 7, 6, 5, 4, 3], [2], _232)
 T [37] Call: lists:append([7, 6, 5, 4, 3], [2], _3664)
 T [46] Call: lists:append([6, 5, 4, 3], [2], _4554)
 T [55] Call: lists:append([5, 4, 3], [2], _5444)
 T [64] Call: lists:append([4, 3], [2], _6334)
 T [73] Call: lists:append([3], [2], _7224)
 T [82] Call: lists:append([], [2], _8114)
 T [82] Exit: lists:append([], [2], [2])
 T [73] Exit: lists:append([3], [2], [3, 2])
 T [64] Exit: lists:append([4, 3], [2], [4, 3, 2])
 T [55] Exit: lists:append([5, 4, 3], [2], [5, 4, 3, 2])
 T [46] Exit: lists:append([6, 5, 4, 3], [2], [6, 5, 4, 3, 2])
 T [37] Exit: lists:append([7, 6, 5, 4, 3], [2], [7, 6, 5, 4, 3, 2])
 T [28] Exit: lists:append([8, 7, 6, 5, 4, 3], [2], [8, 7, 6, 5, 4, 3, 2])
 T [19] Exit: rev([2, 3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3, 2])
 T [19] Call: lists:append([8, 7, 6, 5, 4, 3, 2], [1], _18)
 T [28] Call: lists:append([7, 6, 5, 4, 3, 2], [1], _15684)
 T [37] Call: lists:append([6, 5, 4, 3, 2], [1], _16574)
 T [46] Call: lists:append([5, 4, 3, 2], [1], _17464)
 T [55] Call: lists:append([4, 3, 2], [1], _18354)
 T [64] Call: lists:append([3, 2], [1], _19244)
 T [73] Call: lists:append([2], [1], _20134)
 T [82] Call: lists:append([], [1], _21024)
 T [82] Exit: lists:append([], [1], [1])
 T [73] Exit: lists:append([2], [1], [2, 1])
 T [64] Exit: lists:append([3, 2], [1], [3, 2, 1])
 T [55] Exit: lists:append([4, 3, 2], [1], [4, 3, 2, 1])
 T [46] Exit: lists:append([5, 4, 3, 2], [1], [5, 4, 3, 2, 1])
 T [37] Exit: lists:append([6, 5, 4, 3, 2], [1], [6, 5, 4, 3, 2, 1])
 T [28] Exit: lists:append([7, 6, 5, 4, 3, 2], [1], [7, 6, 5, 4, 3, 2, 1])
 T [19] Exit: lists:append([8, 7, 6, 5, 4, 3, 2], [1], [8, 7, 6, 5, 4, 3, 2|...])
 T [10] Exit: rev([1, 2, 3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3, 2, 1])
L = [8, 7, 6, 5, 4, 3, 2, 1].

Prolog

two examples

classic logic problem: Monkey and Banana

The Monkey and the Banana
There is a monkey standing at the door to a room. In the middle of the room, a banana is hanging from the ceiling. The monkey is hungry and wants the banana, but he cannot reach it from the floor. At the window, there is a box that the monkey can use.

classic logic problem: Monkey and Banana

What can the monkey do?

Can the monkey get the banana? If so, how?

classic logic problem: Monkey and Banana

The world of the room, the monkey, the box, the banana, and the actions the monkey can perform can be represented by a state.

The state is a Prolog structure.

What should be in the state?

classic logic problem: Monkey and Banana

State: state(H,V,B,HB)


Goal: state(_,_,_,has)

classic logic problem: Monkey and Banana

Actions change state (state(H,V,B,HB)) of monkey

How can the state change?

What rules must be used in changing state?


Possible actions (act(...)):

Let's define actions as: act(PrevState, Action, NextState)

classic logic problem: Monkey and Banana

How would we implement this?

/*
reminder: state(H, V, B, HB)
    H,B horizontal positions: atdoor, atwindow, middle
    V   vertical positions: onfloor, onbox
    HB  has banana or has not: has, hasnot
*/

/* goal: reach state where monkey has banana */
canget( state(_, _, _, has) ).

/* canget(State1) is true if we can transition from State1
   to State2 via Action and reach banana from State2 */
canget(State1) :- act(State1, Action, State2), canget(State2).

/* initial starting condition */
go :- canget( state(atdoor, onfloor, atwindow, hasnot) ).

/* Action: grasp, climb, push(H1, H2), walk(H1, H2) */
act(PrevState, Action, NextState) :- ...

run with ?- go.

classic logic problem: Monkey and Banana

Executing the Prolog program returns true., but it does not tell how. How do we improve this?

Furthermore, it returns many, many trues. Why?

ideone ]

classic logic problem: Monkey and Banana

Improved Prolog solution

ideone ]

another example: sudoku

Prolog is excellent at solving combinatorial tasks such as Sudoku!

1 8 4
2 4 5 6
3 2 5
4 8 5
7 8 9 5
6 2 3
8 1 7
1 2 3 8
2 5 9

another example: sudoku

:- use_module(library(clpfd)).  /* helps solve combinatorial problems */
                                /* defines: all_distinct/1 and ins/2  */

/* describe correct sudoku puzzle */
sudoku(Rows) :-
        /* sudoku puzzles are 9x9 grid: 9rows and 9cols */
        length(Rows, 9), maplist(length_(9), Rows),
        /* all values in puzzle are 1--9 */
        append(Rows, Vs), Vs ins 1..9,
        /* each row contains distinct values (1--9) */
        maplist(all_distinct, Rows),
        /* each col contains distinct values (1--9) */
        transpose(Rows, Columns), maplist(all_distinct, Columns),
        /* each block contains distinct values (1--9) */
        Rows = [A,B,C,D,E,F,G,H,I],
        blocks(A, B, C), blocks(D, E, F), blocks(G, H, I).

/* solve and then render puzzle */
solve_sudoku(Puzzle) :- sudoku(Puzzle), show_sudoku(Puzzle).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% HELPER FUNCTIONS

length_(L, Ls) :- length(Ls, L).

/* break 3rows into 3x3 blocks; each block contains distinct vals */
blocks([], [], []).
blocks([N1,N2,N3|Ns1], [N4,N5,N6|Ns2], [N7,N8,N9|Ns3]) :-
        all_distinct([N1,N2,N3,N4,N5,N6,N7,N8,N9]),
        blocks(Ns1, Ns2, Ns3).

/* render puzzle */
show_sudoku(Puzzle) :-
        maplist(label, Puzzle), maplist(portray_clause, Puzzle).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PUZZLES

problem(1, P) :- /* shokyuu */
        P = [[1,_,_,8,_,4,_,_,_],
             [_,2,_,_,_,_,4,5,6],
             [_,_,3,2,_,5,_,_,_],
             [_,_,_,4,_,_,8,_,5],
             [7,8,9,_,5,_,_,_,_],
             [_,_,_,_,_,6,2,_,3],
             [8,_,1,_,_,_,7,_,_],
             [_,_,_,1,2,3,_,8,_],
             [2,_,5,_,_,_,_,_,9]].


problem(2, P) :-  /* shokyuu */
        P = [[_,_,2,_,3,_,1,_,_],
             [_,4,_,_,_,_,_,3,_],
             [1,_,5,_,_,_,_,8,2],
             [_,_,_,2,_,_,6,5,_],
             [9,_,_,_,8,7,_,_,3],
             [_,_,_,_,4,_,_,_,_],
             [8,_,_,_,7,_,_,_,4],
             [_,9,3,1,_,_,_,6,_],
             [_,_,7,_,6,_,5,_,_]].

problem(3, P) :-
        P = [[1,_,_,_,_,_,_,_,_],
             [_,_,2,7,4,_,_,_,_],
             [_,_,_,5,_,_,_,_,4],
             [_,3,_,_,_,_,_,_,_],
             [7,5,_,_,_,_,_,_,_],
             [_,_,_,_,_,9,6,_,_],
             [_,4,_,_,_,6,_,_,_],
             [_,_,_,_,_,_,_,7,1],
             [_,_,_,_,_,1,_,3,_]].


×