Principles of Programming Languages, 4

Matteo Pradella

February 2015

Matteo Pradella Principles of Programming Languages, 4 February 2015 1 / 49 1 :

2 The Prolog Language

Matteo Pradella Principles of Programming Languages, 4 February 2015 2 / 49 Introduction to Prolog

1 Created around 1972 by Alain Colmerauer with Philippe Roussel, based on Robert Kowalski’s procedural interpretation of Horn clauses. 2 decidable subset: Datalog, a query and rule language for deductive databases 3 its failure as a mainstream language traditionally due to the following: 4 Prolog usage in Fifth Generation Computer Systems project (FGCS) 5 FCGS was an initiative by Japan’s Ministry of International Trade and Industry, begun in 1982, to create a "fifth generation computer"

Matteo Pradella Principles of Programming Languages, 4 February 2015 2 / 49 Prolog in the real world

1 Prolog is not used much nowadays 2 still, for some activities it can be very useful - probably it was just a bad idea to use it as a system . There are implementations usable for parts of the logic of complex applications e.g. written in Java 3 first of all: rapid prototyping. E.g. Prolog was used at Ericsson for implementing the first Erlang 4 other usages: some academic intrusion detection systems; event handling middlewares; some Nokia phones with the defunct MeeGoo; IBM’s Watson is written in Java, C++, and Prolog.

Matteo Pradella Principles of Programming Languages, 4 February 2015 3 / 49 Prolog and logic: Horn clauses

1 in general, a logic program has the following form:

^ i i i i  ∀X1 ... Xm φ ⇐ θ1 ∧ θ2 ∧ ... ∧ θn i

2 note that φ ⇐ (θ1 ∨ θ2) ∧ θ3 0 0 0 3 is equivalent to (φ ⇐ θ ∧ θ3) ∧ (θ ⇐ θ1) ∧ (θ ⇐ θ2) 4 we start from a goal ϕ, and follow the implications "backward", until we reach a solution or we fail (we could also loop. . . )

Matteo Pradella Principles of Programming Languages, 4 February 2015 4 / 49 A first example

1 A logic program: 1 ∀X , L find(cons(X , L), X ) 2 ∀X , Y , L (find(cons(Y , L), X ) ⇐ find(L, X )) 2 i.e. (being (A ⇐ B) ⇐⇒ (¬A ⇒ ¬B)): 1 ∀X , L find(cons(X , L), X ) 2 ∀X , Y , L (¬find(cons(Y , L), X ) ⇒ ¬find(L, X ))

Matteo Pradella Principles of Programming Languages, 4 February 2015 5 / 49 A first example (cont.)

1 example query: find([1, 2, 3], X )? 2 from a logic point of view we are stating false ⇐ find([1, 2, 3], X ), i.e. we try to find a counterexample 3 it is the same as trying to satisfy ∀X ¬find([1, 2, 3], X ) 4 first of all: Prolog is based on the closed world assumption: if something is not stated or deducible, it is false 5 we use the 1st clause, with X = 1, and L = [2, 3]: 6 find(cons(1, [2, 3]), 1), so we have a contradiction with X = 1.

Matteo Pradella Principles of Programming Languages, 4 February 2015 6 / 49 A first example (cont.)

1 another query: false ⇐ find([1, 2, 3], 2), i.e. ¬find([1, 2, 3], 2) 2 2nd clause: ¬find(cons(1, [2, 3]), 2) ⇒ ¬find([2, 3], 2) 3 Modus Ponens: ¬find([2, 3], 2) 4 but this is contradicts an instance of the 1st clause: find(cons(2, [3]), 2). 5 hence, ¬find([1, 2, 3], 2) does not hold.

Matteo Pradella Principles of Programming Languages, 4 February 2015 7 / 49 A first example (cont.)

1 using the same technique, we obtain the following results: 2 find(X , 5)? gives X = cons(5, V ) 3 find([1, 2, X , 5], 5)? gives X = 5 4 find([1, 2, X , 5], Y )? gives Y = 1 5 in practice, Prolog has a lot of extra-logical commands and techniques, so it is often useful to consider its procedural semantics

Matteo Pradella Principles of Programming Languages, 4 February 2015 8 / 49 The Prolog Language: Syntax

1 Constants: usual numbers, "strings" 2 but also atoms: test void = := ’this-name’ ’hello world’ [] 3 Variables: must start with either a capital letter (Variable, VAR, X) or _ (the usual "don’t care", usually called anonymous in Prolog jargon) 4 compound terms represent everything else, from procedure to data structures (homoiconicity)

Matteo Pradella Principles of Programming Languages, 4 February 2015 9 / 49 Syntax for terms

1 standard syntax is e.g. f(g(3), X), 2 the name of the applied function (f or g in the term before), is also called functor 3 usually the arity of a functor is indicated like this: f/2, g/1 4 there are shortcuts for infix operators: 2+3/4 stands for +(2,/(3,4))

Matteo Pradella Principles of Programming Languages, 4 February 2015 10 / 49 Lists

1 are terms like the others 2 the empty list is [], while . is cons 3 e.g. .(1,.(2,.(3,[]))) 4 special syntax: the usual [ 1,2,3] 5 | is used to access car and cdr: the expression [X | L] represents a pair in a list, where X is car, while L is cdr

Matteo Pradella Principles of Programming Languages, 4 February 2015 11 / 49 Strings vs atoms

1 strings are quite strange: like in Haskell, a string is a list; unlike Haskell, a character is represented by a number holding its ASCII code 1 e.g. "hello" stands for [104, 101, 108, 108, 111] 2 (but: there are utilities for managing unicode strings in modern implementations) 2 atoms are like symbols in Scheme 3 atoms are not strings: anAtom, ’this is another atom’, "this is a string"

Matteo Pradella Principles of Programming Languages, 4 February 2015 12 / 49 Programs

1 we saw the basic idea with logic formulae and Horn clauses 2 syntax: ⇐ is written :-, ∧ is comma, ∨ is semicolon 3 going back to our simple example: % a very first purely logical example find([X|_], X). % you cannot do this in Haskell (§) find([Y|L], X) :- find(L, X). 4 we usually save it to a file (containing facts), then load it in the Prolog system (with [ex].) 5 at this point, we can perform queries, e.g. find([ 1,2,3], 2).

Matteo Pradella Principles of Programming Languages, 4 February 2015 13 / 49 Our example, rewritten

1 this is an equivalent variant of the program (with ;) find([Y|L], X) :- Y = X ; find(L, X). 2 like in Haskell, Prolog reads the clauses top to bottom, from left to right 3 the head of the clause is matched with the procedure call, by using an algorithm called unification, stronger than Haskell’s pattern matching (see §)

4 clauses are also called sentences, having a head and a body

Matteo Pradella Principles of Programming Languages, 4 February 2015 14 / 49 Procedural semantics

1 To execute a goal, the system searches forwards from the beginning of the program for the first clause whose head matches with the goal

1 the unification process [Robinson 1965] finds the most general common instance of the two terms, which is unique if it exists (see next slide) 2 if a match is found, the matching clause instance is then activated by executing in turn, from left to right, each of the goals in its body. 2 If at any time the system fails to find a match for a goal, it backtracks, i.e. it rejects the most recently activated clause, undoing any substitutions made by the match with the head of the clause

1 next it reconsiders the original goal which activated the rejected clause, and tries to find a subsequent clause which also matches the goal.

Matteo Pradella Principles of Programming Languages, 4 February 2015 15 / 49 Unification (=)

1 An uninstantiated variable can be unified with an atom, a term, or another uninstantiated variable (it becomes an alias). 1 e.g. X = f(3), X = bob, X = Y 2 Two atoms can only be unified if they are identical. 1 e.g. 3 = 3, bob = bob, 3 6= bob 3 A term can be unified with another term if the top function symbols and arities of the terms are identical, and if all the parameters can be unified (recursion). 1 e.g. f(g(3),Y) = f(Z,bob), 3+X 6= Z-Y, [X,2|Y] = [1,2,3,4]

Matteo Pradella Principles of Programming Languages, 4 February 2015 16 / 49 Occur check

1 unification does not have an "occur check", i.e. when unifying a variable against a term the system does not check whether the variable occurs in the term; 2 when the variable occurs in the term, unification should fail, but the absence of the check means that the unification succeeds, producing a circular term.

Matteo Pradella Principles of Programming Languages, 4 February 2015 17 / 49 Occur check (cont.)

1 The absence of the occur check is not a bug or design oversight, but a design decision: 1 unification with the occur check is at best linear on the sum of the sizes of the terms being unified 2 while without the occur check it is linear on the size of the smallest of the terms being unified 2 Unification against a variable should be thought of as the basic operation of Prolog, but this can take constant time only if the occur check is omitted.

Matteo Pradella Principles of Programming Languages, 4 February 2015 18 / 49 a classical example: genealogy

1 every book on Prolog has a variant of this example: grandfather(X,Z) :- parent(X,Y), father(Y,Z). grandmother(X,Z) :- parent(X,Y), mother(Y,Z). parent(X,Y) :- mother(X,Y). parent(X,Y) :- father(X,Y).

father(gino, adamo). mother(gino, elena). father(ella, gino). mother(ella, vita). father(ugo, gino). mother(ugo, vita).

Matteo Pradella Principles of Programming Languages, 4 February 2015 19 / 49 a classical example (cont.)

1 we can try it with some queries: ?- grandfather(X, adamo). X = ella .

?- grandfather(X, adamo). X = ella ; X = ugo.

?- grandmother(ugo, Y). Y = elena.

Matteo Pradella Principles of Programming Languages, 4 February 2015 20 / 49 Another example

1 as we saw before, there is no clear role of input and output in a procedure call 2 consider the following example: concatenate([X|L1],L2,[X|L3]) :- concatenate(L1,L2,L3). concatenate([],L,L). 3 in this case concatenate is usually called with the first two parameters for input, and the last one for the result 4 e.g. ?- concatenate([1,2,3],[4,5,6],X). X = [1, 2, 3, 4, 5, 6]. 5 Note: it is common to use the last parameter for the result 6 Note II: concatenate is called append in the library

Matteo Pradella Principles of Programming Languages, 4 February 2015 21 / 49 Parameters and result

1 we may also use it in this way: ?- concatenate(X,[2,Y],[1,1,1,2,3]). X = [1, 1, 1], Y = 3 . 2 or this: ?- concatenate(X,[2,Y],[1,1,2,3]). X = [1, 1], Y = 3 ; % are there other solutions? false. % no

Matteo Pradella Principles of Programming Languages, 4 February 2015 22 / 49 Parameters and result (cont.)

1 or also in this way: ?- concatenate(X,Y,[1,1,1,2,3]). X = [1, 1, 1, 2, 3], Y = [] ; X = [1, 1, 1, 2], Y = [ 3 ] ; X = [1, 1, 1], Y = [2, 3] ; X = [1, 1], Y = [1, 2, 3] ; X = [ 1 ], Y = [1, 1, 2, 3] ; X = [], Y = [1, 1, 1, 2, 3].

Matteo Pradella Principles of Programming Languages, 4 February 2015 23 / 49 An exercise: non-deterministic Push-down Automata (NPDA)

1 it is very easy to simulate a NPDA with Prolog 2 main idea: define a predicate config which represents the configuration of the automaton at a given step of the computation 3 config has three parameters 1 the first one is the current state 2 the second one is the current stack 3 the third one is the part of the input string that we still have to read 4 it is natural to use lists for representing the stack and the input string 5 e.g. we start with config(q0, [z0], [a,a,a,b,b,b]) if we are considering an automaton for anbn

Matteo Pradella Principles of Programming Languages, 4 February 2015 24 / 49 NPDA: solution

1 we check acceptance, when the input string is over: config(State, _, []) :- final(State). 2 standard move: config(State, [Top|Rest], [C|String]) :- delta(State, C, Top, NewState, Push), append(Push, Rest, NewStack), config(NewState, NewStack, String). 3 -moves are just a variant: config(State, [Top|Rest], String) :- delta(State, epsilon, Top, NewState, Push), append(Push, Rest, NewStack), config(NewState, NewStack, String). 4 run(Input) :- initial(Q), config(Q, [z0], Input).

Matteo Pradella Principles of Programming Languages, 4 February 2015 25 / 49 using NPDA

1 we want to try with a nondeterministic language, so we take L = {anbn} ∪ {anb2n} delta(q0, a, z0, q1, [a, z0]). % a^n b^2n delta(q1, a, a, q1, [a, a]). delta(q1, b, a, q2, [a]). delta(q2, b, a, q3, []). delta(q3, b, a, q2, [a]). delta(q3, epsilon, z0, q4, []).

delta(q0, a, z0, q11, [a, z0]). % a^n b^n delta(q11, a, a, q11, [a, a]). delta(q11, b, a, q21, []). delta(q21, b, a, q21, []). delta(q21, epsilon, z0, q4, []).

initial(q0). final(q4).

Matteo Pradella Principles of Programming Languages, 4 February 2015 26 / 49 using NPDA (cont.)

1 example queries: ?- run([a,a,a,b,b,b]). true .

?- run([a,a,b,b,b,b]). true .

?- run([a,a,b,b,b]). false.

?- run([a,a,b|X]). X = [b, b, b, epsilon] ; X = [b, b, b] ; X = [b, epsilon] ; X = [b] ; false.

Matteo Pradella Principles of Programming Languages, 4 February 2015 27 / 49 Extra-logical constructs: cut

1 efficient logic programming is not trivial: it is often hard to understand the computing steps taken by the system 2 for this reason, there is a particular extra-logical construct that the programmer can use to improve the search for the goal 3 this construct is called cut, written ! 4 its name comes from the fact that it can be used to prune branches in the depth-first search performed by the system 5 hence, we are discarding all the backtrack information that was stored during the run

Matteo Pradella Principles of Programming Languages, 4 February 2015 28 / 49 Another exercise on Push-down Automata

1 define a deterministic implementation of PDA 2 optimize it using cut, if possible 3 (is it possible to use cut for NPDA? Why?)

Matteo Pradella Principles of Programming Languages, 4 February 2015 29 / 49 Deterministic PDA

1 here is the solution: % acceptance config(State, _, []) :- final(State), !.

% standard move config(State, [Top|Rest], [C|String]) :- delta(State, C, Top, NewState, Push), !, append(Push, Rest, NewStack), config(NewState, NewStack, String).

% epsilon move config(State, [Top|Rest], String) :- delta(State, epsilon, Top, NewState, Push), !, append(Push, Rest, NewStack), config(NewState, NewStack, String).

Matteo Pradella Principles of Programming Languages, 4 February 2015 30 / 49 Numerical expressions

1 theoretically, we do not need to introduce number, as we may use the functor succ and the atom zero. For instance, we can represent 5 as succ(succ(succ(succ(succ(zero))))) 2 but this is very inefficient, so Prolog uses integers and floating point numbers provided by the machine 3 note that numerical expressions are treated like other terms, so they are not usually evaluated

Matteo Pradella Principles of Programming Languages, 4 February 2015 31 / 49 Numerical expressions (cont.)

1 e.g. we could try to define a times predicate as follows: times(_,0,0). times(0,_,0). times(1,X,X). times(X,1,X). times(X,Y,Z) :- Z = X*Y. 2 but if we perform the query times(3,5,X), we obtain X = 3 ∗ 5, instead of 15

Matteo Pradella Principles of Programming Languages, 4 February 2015 32 / 49 Numerical expressions (cont.)

1 another (bad) example: pow(_,0,1). pow(X,1,X). pow(X,N,R) :- pow(X,N-1,R1), R = X*R1. % two errors 2 this loops for N > 1, because it calls pow(X , N − 1, ...), pow(X , N − 1 − 1, ...),...

Matteo Pradella Principles of Programming Languages, 4 February 2015 33 / 49 Numerical expressions (cont.)

1 for this reason there are a couple of useful predicates for managing numbers: =:= and is 2 e.g. the query X is 2/3 returns X = 0.6666666666666666 3 the query 3 + 1 =:= 6 − 2 returns true

Matteo Pradella Principles of Programming Languages, 4 February 2015 34 / 49 Numerical expressions (cont.)

1 a better version: pow(_,0,1). pow(X,1,X). pow(X,N,R) :- N1 is N-1, pow(X,N1,R1), R is X*R1. 2 a problem: ?- pow(2,16,8). ERROR: Out of local stack 3 how can we fix it?

Matteo Pradella Principles of Programming Languages, 4 February 2015 35 / 49 Numerical expressions (cont.)

1 a yet better version uses cut: pow(_,0,1) :- !. pow(X,1,X) :- !. pow(X,N,R) :- !, N1 is N-1, pow(X,N1,R1), R is X*R1. 2 with this one: ?- pow(2,16,8). false.

Matteo Pradella Principles of Programming Languages, 4 February 2015 36 / 49 A quicksort variant

1 first, a way to partition lists: part([X|L],Y,[X|L1],L2) :- X =< Y, !, part(L,Y,L1,L2). part([X|L],Y,L1,[X|L2]) :- X > Y, !, part(L,Y,L1,L2). part([],_,[],[]). 2 and now quicksort: qsort0([],[]). qsort0([H|T],Sorted):- part(T,H,L1,L2), !, qsort0(L1,Sorted1), qsort0(L2,Sorted2), append(Sorted1,[H|Sorted2],Sorted).

Matteo Pradella Principles of Programming Languages, 4 February 2015 37 / 49 A (more efficient) quicksort variant

1 we can avoid the append to make it more efficient, by using another parameter as an accumulator qsort([X|L],R0,R) :- part(L,X,L1,L2), !, qsort(L2,R0,R1), qsort(L1,[X|R1],R). qsort([],R,R). 2 e.g. ?- qsort([4,3,1,13,32,-3,32],[],X). X = [-3, 1, 3, 4, 13, 32, 32].

Matteo Pradella Principles of Programming Languages, 4 February 2015 38 / 49 tracing with SWI-Prolog

1 it is sometimes useful to trace a computation, e.g. trace(qsort). [debug] ?- qsort([2,3,1],[],X). T Call: (6) qsort([2, 3, 1], [], _G367) T Call: (7) qsort([3], [], _G486) T Call: (8) qsort([], [], _G486) T Exit: (8) qsort([], [], []) T Call: (8) qsort([], [3], _G489) T Exit: (8) qsort([], [3], [3]) T Exit: (7) qsort([3], [], [3]) T Call: (7) qsort([1], [2, 3], _G367) T Call: (8) qsort([], [2, 3], _G492) T Exit: (8) qsort([], [2, 3], [2, 3]) T Call: (8) qsort([], [1, 2, 3], _G367) T Exit: (8) qsort([], [1, 2, 3], [1, 2, 3]) T Exit: (7) qsort([1], [2, 3], [1, 2, 3]) T Exit: (6) qsort([2, 3, 1], [], [1, 2, 3]) X = [1, 2, 3]. 2 also, old fashioned debugging with print/1 and nl/0.

Matteo Pradella Principles of Programming Languages, 4 February 2015 39 / 49 Higher-order and meta predicates

1 Prolog is based on first-order logic 1 so we cannot e.g. match X(Y) = f(3) 2 nonetheless, for practical reasons there are a number of external and "meta" predicates 3 e.g. call, which is used to perform a query in a program 4 call(G1, A1, A2, ...) adds arguments A1, A2, . . . to goal G1 and performs the resulting query 5 e.g. ?- call(plus(1), 2, X). X = 3. % i.e. plus(1,2,X).

Matteo Pradella Principles of Programming Languages, 4 February 2015 40 / 49 Higher-order and meta predicates: an example

1 here is an implementation of map (usually called maplist in the library) map(_, [], []). map(C, [X|Xs], [Y|Ys]) :- call(C, X, Y), map(C, Xs, Ys). 2 e.g. if we define test(N,R):- R is N*N. ?- map(test,[1,2,3,4],X). X = [1, 4, 9, 16].

Matteo Pradella Principles of Programming Languages, 4 February 2015 41 / 49 Higher-order and meta predicates (cont.)

1 other predicates are used for adding facts at runtime:

1 asserta(F) is used to state F as a first clause 2 assertz(F) is the same, but as the last clause 2 this is also useful to add facts at the REPL, e.g. ?- assertz(test(N,R):- R is N*N). 3 another peculiar "meta-predicate" is var: succeeds when its argument is a variable 1 CAVEAT: var(X) succeeds without binding X!

Matteo Pradella Principles of Programming Languages, 4 February 2015 42 / 49 Higher-order and meta predicates (cont.)

1 Moreover, we know that we cannot match X (Y ) = f (3), but sometimes we need something analogous 2 we can do it by using the predicate =.. which is used to decompose a term 3 e.g. the query f(2,g(4)) =.. X binds X to the list [f, 2, g(4)] 4 so in the previous case we can do f(3) =.. [X,Y]

Matteo Pradella Principles of Programming Languages, 4 February 2015 43 / 49 Negation

1 fail is quite naturally used to fail. 2 using call, !, and fail we can define negation: not(X) :- call(X), !, fail. not(X). 3 e.g. ?- not(member(6,[1,2,3])). true. 4 not is already defined, it is the prefix operator \+ 1 like in: \+ member(6,[1,2,3]).

Matteo Pradella Principles of Programming Languages, 4 February 2015 44 / 49 Infix predicates

1 infix predicates can be defined (almost) like in Haskell 2 e.g. :- op(800, yfx, =>). % left associative :- op(900, yfx, &). % likewise, with lower priority :- op(600, xfy, ->). % right associative :- op(300, xfx, :). % not associative :- op(900, fy, \+). % prefix not 3 note the starting :-, because they are commands 4 e.g. ?- assert(:- op(600, xfy, ->)). true. ?- (a -> b -> c) =.. X. X = [->, a, (b->c)].

Matteo Pradella Principles of Programming Languages, 4 February 2015 45 / 49 Example: a symbolic differentiator

1 basic rules d(U+V,X,DU+DV) :- !, d(U,X,DU), d(V,X,DV). d(U-V,X,DU-DV) :- !, d(U,X,DU), d(V,X,DV). d(U*V,X,DU*V+U*DV) :- !, d(U,X,DU), d(V,X,DV). d(U^N,X,N*U^N1*DU) :- !, integer(N), N1 is N-1, d(U,X,DU). d(-U,X,-DU) :- !, d(U,X,DU). 2 terminating rules d(X,X,1) :- !. d(C,_,0) :- atomic(C), !. 3 atomic holds with atoms and numbers

Matteo Pradella Principles of Programming Languages, 4 February 2015 46 / 49 a differentiator (cont.)

1 terminating rules (cont.) d(sin(X),X,cos(X)) :- !. d(cos(X),X,-sin(X)) :- !. d(exp(X),X,exp(X)) :- !. d(log(X),X,1/X) :- !. 2 chain rule d(F_G,X,DF*DG) :- F_G=..[_,G], !, d(F_G,G,DF), d(G,X,DG). 3 note that: 1+2/3 =.. X binds X to [+,1,2/3]

Matteo Pradella Principles of Programming Languages, 4 February 2015 47 / 49 a differentiator (cont.)

1 now, let us try it: ?- d(2*sin(cos(x+cos(x))), x, V). V = 0*sin(cos(x+cos(x)))+2* (cos(cos(x+cos(x)))* (-sin(x+cos(x))* (1+ -sin(x)))).

Matteo Pradella Principles of Programming Languages, 4 February 2015 48 / 49 Legal stuff

1 ©2012-2015 by Matteo Pradella 2 Licensed under Creative Commons License, Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)

Matteo Pradella Principles of Programming Languages, 4 February 2015 49 / 49