280 likes | 416 Views
Formal Models of Computation Part II The Logic Model. Lecture 6 – Arithmetic, fail and the cut. Arithmetic. X. +. *. 10. 2. 4. Arithmetic expressions trees like any other terms: Prolog does not treat arithmetic expressions differently, unless asked to do so.
E N D
Formal Models of ComputationPart IIThe Logic Model Lecture 6 – Arithmetic, fail and the cut
Arithmetic X + * 10 2 4 • Arithmetic expressions trees like any other terms: • Prolog does not treat arithmetic expressions differently, unless asked to do so. • In order to evaluate arithmetic expressions we use the built-in “is” ?- X = 10 + (2 * 4). X = 10 + (2 * 4) ? yes ? - ?- X is 10 + (2 * 4). X = 18 ? yes ? - formal models of computation
Arithmetic • The syntax of the built-in “is” is: VariableisArithmeticExpression • For instance: Result is (10 * 7) NewRes is OldRes + 1 • Variables that appear on the expression • Must be instantiated when expression is evaluated • Otherwise execution error! • We can delay the evaluation until all variables have value: ?- Exp = A + B, A = 3, B = 4, Res is Exp. A = 3, B = 4, Exp = 3 + 4 Res = 7 formal models of computation
Evaluation in Haskell and Prolog • In Haskell, expressions are always evaluated if they contribute to the final result. Terms stand for their results. • In Prolog, every term stands for itself. Evaluation only happens when explicitly invoked by a built-in like “is”. • Prolog functors (even arithmetic ones) are like Haskell constructors (e.g. :) formal models of computation
Arithmetic: an Example • How can we find the size (no. elements) of a list? • Simple recursive formulation: • The number of elements of the empty list is zero • The number of elements of a list [X|Xs] is one plus the number of elements of Xs. • Predicate length with 2 arguments: • 1st argument is the list • 2nd argument is the size of the list • For example: ?- length([a,b,c,d],Length). Length = 4 ? ?- length([1,[2,3],4],L). L = 3 ? ?- formal models of computation
Arithmetic: an Example length(L,S):- % S is the length of list L L = [], % if L is an empty list S = 0. % its length S is zero length(L,S):- % otherwise L = [X|Xs], % if L is a list [X|Xs] S is 1 + length(Xs,S). % its length is 1 plus length of tail Careful! Predicate calls are true or false! This expression does not make sense! • First attempt length(L,S):- % S is the length of list L L = [], % if L is an empty list S = 0. % its length S is zero length(L,S):- % otherwise L = [X|Xs], % if L is a list [X|Xs] S is 1 + length(Xs,S). % its length is 1 plus length of tail length(L,S):- L = [], % if L is an empty list S = 0. % its length S is zero length(L,S):- % otherwise L = [X|Xs], % if L is a list [X|Xs] S is 1 + length(Xs,S). % its length is 1 plus length of tail formal models of computation
Arithmetic: an Example length(L,S):- % S is the length of list L L = [], % if L is an empty list S = 0. % its length S is zero length(L,S):- % otherwise L = [X|Xs], % if L is a list [X|Xs] S is 1 + SXs, % length S is 1 plus length of tail length(Xs,SXs). % get the length SXs of tail Xs Careful! When this expression is evaluated, the value of SXs won’t yet exist!! • Second attempt(Can you see what’s wrong?) length(L,S):- % S is the length of list L L = [], % if L is an empty list S = 0. % its length S is zero length(L,S):- % otherwise L = [X|Xs], % if L is a list [X|Xs] S is 1 + SXs, % length S is 1 plus length of tail length(Xs,SXs). % get the length SXs of tail Xs formal models of computation
Arithmetic: an Example length([],0). % the empty list has length 0 length([_|Xs],S):- % a non-empty list [_|Xs] has size S length(Xs,SXs), % get the length SXs of its tail Xs S is 1 + SXs. % add 1 to the length of its tail Anonymous variable, used as “place holder”. In this context, we don’t care what the value of the element is – we just want to count them!! • Third attempt: • Simplified version: length(L,S):- % S is the length of list L L = [], % if L is an empty list S = 0. % its length S is zero length(L,S):- % otherwise L = [X|Xs], % if L is a list [X|Xs] length(Xs,SXs), % get the length SXs of its tail Xs S is 1 + SXs. % add 1 to the length of its tail length([],0). % the empty list has length 0 length([_|Xs],S):- % a non-empty list [_|Xs] has size S length(Xs,SXs), % get the length SXs of its tail Xs S is 1 + SXs. % add 1 to the length of its tail formal models of computation
Arithmetic: Summary • Arithmetic: via “is” built-in. • Some operators: +, –, *, / (add, subtract, multiply, divide) // (integer division) mod (modulo) • All variables on expression must have values, otherwise execution error!! • Because of this restriction, we ought to bear in mind the order in which Prolog proves/executes the body of a clause (or a query). formal models of computation
Failure-driven loops • Prolog offers a built-in predicate fail which always fails: • We can use this predicate to define a failure-driven loop, an alternative to recursion: ?- fail. no p(a). p(b). p(c). loopFail:- % loopFail succeeds if p(X), % we can prove p(X) and write(X), % write the value of X and nl, % skip a line. fail. % fail and BACKTRACK!! loopFail. % if no (more) answers, stop formal models of computation
Failure-driven loops loopFail:- p(X1),write(X1),nl,fail. {X1/a} loopFail:- p(X1),write(X1),nl,fail. {X1/a} Backtrack!! Backtracking skips over built-ins!! loopFail:- p(X1),write(X1),nl,fail. {X1/b} Backtrack!! loopFail:- p(X1),write(X1),nl,fail. {X1/c} Backtrack!! • Execution: loopFail:- p(X1),write(X1),nl,fail. loopFail:- p(X1),write(X1),nl,fail. loopFail:- p(X1),write(X1),nl,fail. {X1/a} loopFail:- p(X1),write(X1),nl,fail. {X1/a} p(a). p(b). p(c). loopFail:- p(X), write(X), nl, fail. loopFail. loopFail:- p(X1),write(X1),nl,fail. {X1/b} loopFail:- p(X1),write(X1),nl,fail. loopFail:- p(X1),write(X1),nl,fail. {X1/b} loopFail:- p(X1),write(X1),nl,fail. {X1/c} loopFail:- p(X1),write(X1),nl,fail. loopFail:- p(X1),write(X1),nl,fail. {X1/c} ?- loopFail. a b ?- loopFail. a b c yes ?- ?- loopFail. a ?- loopFail. ?- loopFail. a b c loopFail:- p(X1),write(X1),nl,fail. No more values for p(X)! loopFail. formal models of computation
Controlling Backtracking via cuts (!) • Prolog’s backtracking mechanism may, in some cases, lead to inefficiencies. • We can control backtracking via the built-in “!”, called “cut”. • The “!” is used as an ordinary predicate, in the body of the clause or query – it always succeeds! • However, the “!” causes the execution to commit to the current clause and to the solutions (proofs) of the goals to its left. • Example: p(A,B,C,D):- q(A), r(A,B), !, s(B,C),t(A,D). formal models of computation
Controlling Backtracking via cuts (!) r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/a} Fail!!Backtrack… ?- r(Ans). Ans = 2 ? ; ?- r(Ans). Ans = 2 ? ; no ?- Force backtrack… Force backtrack… r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/b,Y1/2} No other proof for s(Y1)! r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/b,Y1/2} Cannot backtrack over “!” • Example: p(a). p(b). p(c). q(b,2). q(c,3). s(2). s(3). r(Y):- p(X),q(X,Y),!,s(Y). r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/a} r(Y1):- p(X1),q(X1,Y1),!,s(Y1). r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/a} r(Y1):- p(X1),q(X1,Y1),!,s(Y1). r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/b,Y1/2} r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/b,Y1/2} r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/b} r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/b} r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/b,Y1/2} r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/b,Y1/2} ?- r(Ans). ?- r(Ans). Ans = 2 ? r(Y1):- p(X1),q(X1,Y1),!,s(Y1). {X1/b,Y1/2} formal models of computation
Commitment in Haskell and Prolog • In some ways, the Prolog cut (!) is similar in spirit to a Haskell guard (|) • Both cause a commitment to the current clause/case • But there are differences as well: • Guards come before the critical tests, cuts come after • Cuts also commit to choices made in subgoals on the left • Let’s look at some ways in which the cut can be useful formal models of computation
Avoiding unnecessary work with “!” • Example: • Suppose • customer(X) picks out a customer from a database sorted (decreasing order) by amount of money spent; • eligiblePrize(X) checks if amount of money spent makes customer eligible to win a prize; • If the best (first) customer does not qualify, why let Prolog try all the other 250000 customers with less money spent? prize(X):- customer(X),eligiblePrize(X). formal models of computation
Avoiding unnecessary work with “!” • Surely, this is a lot better: • This new version saves 250000 unnecessary attempts! • We can only add this cut because we know the first answer is the only one that is any good… prize(X):- customer(X),!,eligiblePrize(X). formal models of computation
Avoiding unnecessary work with “!” ?- 0 > 2. Backtrack! • Another type of case: “disjoint” cases • In Prolog: • Let’s try this: if X < 3 then Y = 0. if X 3 and X < 6 then Y = 1. if X 6 then Y = 2. range(X,0):- X < 3. range(X,1):- X >= 3, X < 6. range(X,2):- X >= 6. range(X,0):- X < 3. range(X,1):- X >= 3, X < 6. range(X,2):- X >= 6. ?- range(1,Y), Y > 2. ?- 1 < 3, 0 > 2. formal models of computation
Avoiding unnecessary work with “!” ?- 1>=3, 1<6, 1 > 2. Backtrack! • Another (more concrete) case: • In Prolog: • Let’s try this: if X < 3 then Y = 0. if X 3 and X < 6 then Y = 1. if X 6 then Y = 2. range(X,0):- X < 3. range(X,1):- X >= 3, X < 6. range(X,2):- X >= 6. ?- range(1,Y), Y > 2. range(X,0):- X < 3. range(X,1):- X >= 3, X < 6. range(X,2):- X >= 6. formal models of computation
Avoiding unnecessary work with “!” ?- 1 >= 6, 2 > 2. Backtrack! ?- range(1,Y), Y > 2. no Fail!! • Another (more concrete) case: • In Prolog: • Let’s try this: if X < 3 then Y = 0. if X 3 and X < 6 then Y = 1. if X 6 then Y = 2. range(X,0):- X < 3. range(X,1):- X >= 3, X < 6. range(X,2):- X >= 6. ?- range(1,Y), Y > 2. range(X,0):- X < 3. range(X,1):- X >= 3, X < 6. range(X,2):- X >= 6. formal models of computation
Avoiding unnecessary work with “!” ?- 0 > 2. Backtrack! ?- range(1,Y), Y > 2. no Fail!! • The values of Y are mutually exclusive • There is no point in trying different clauses!! • Let’s add cuts to reflect this: • The previous query is: range(X,0):- X < 3,!. range(X,1):- X >= 3, X < 6,!. range(X,2):- X >= 6. % no need to add a cut here!! ?- range(1,Y), Y > 2. range(X,0):- X < 3,!. range(X,1):- X >= 3, X < 6,! range(X,2):- X >= 6. ?- 1 < 3, 0 > 2. formal models of computation
Cuts can be necessary: Avoiding bad loops • Example: build a list with decreasing numbers countDown(0,[]). countDown(N,[N|Ns]):- NN is N – 1, countDown(NN,Ns). ?- countDown(5,Nos). ?- countDown(5,Nos). Nos = [5,4,3,2,1] ? ?- countDown(5,Nos). Nos = [5,4,3,2,1] ? ; ?- countDown(5,Nos). Nos = [5,4,3,2,1] ? ; ?- countDown(5,Nos). Nos = [5,4,3,2,1] ? ; ?- countDown(5,Nos). Nos = [5,4,3,2,1] ? ; LOOP!! Cause: Being asked for more solutions, PROLOG will match countDown(0,[]) with the recursive rule. This will force it to compute countDown(-1,Ns), and so on until the stack overflows. formal models of computation
Avoiding “bad” loops with cuts Incidentally… The new countDown on the left still has a problem: if we try ?- countDown(-1,L). The program would loop forever (actually, a stack overflow will stop it). Can you fix it? • Fixing the problem with a “!”: countDown(0,[]):- !. countDown(N,[N|Ns]):- NN is N – 1, countDown(NN,Ns). ?- countDown(5,Nos). ?- countDown(5,Nos). Nos = [5,4,3,2,1] ? ?- countDown(5,Nos). Nos = [5,4,3,2,1] ? ; no ?- formal models of computation
When to use cuts (!) • Cuts aren’t always necessary – don’t add them “just in case”!! • There is usually no reason to add more than one “!” on one clause. • There is no easy way to tell when to use cuts: • You have seen some cases as guidelines… • Is the first answer enough? Do we need all answers? • Is there a risk of an accidental loop? • Will backtracking allow clauses to be wrongly used? • Ultimately, we (programmers) should be able to decide where/if to add cuts… formal models of computation
Cut-Fail Combination • We can combine “!” and “fail” to represent exceptions: • First clause defines when different fails • Second clause is an “else”, where all other cases are dealt; • Notice the anonymous variables – they are not the same!! different(X,X):- !,fail. different(_,_). formal models of computation
Negation as Failure • The cut-fail combination allows us to define a kind of logical negation: • not(Goal) is true if Goal is false • Built-in call(Goal) attempts to prove Goal • For the in-house logicians: • not(G) means “G cannot be proved” • not(G)does not mean “G can be proved false” • Can you tell these apart? • This is called “negation as failure” • It is OK if we adopt the “closed world assumption” not(G):- call(G),!,fail. % if G holds, then fail not(_). % otherwise not(G) holds formal models of computation
Declarative vs. Procedural Meanings • A Prolog program has two “meanings” • Declarative: the logical relationships (i.e. the results) • Procedural: the results and how they were computed • Good Prolog programs: • Exploit the declarative side of logics (relationships) • Take into account procedural aspects (efficiency). • Declarative programs: • Allow for multiple answers and multiple uses of a predicate (e.g., the member predicate) • Procedural programs: • Single answers, single use of predicates formal models of computation
Control in Logic Programming • In logic programming, you have to think about control as well as logic: • To appropriately “time” arithmetic • To avoid loops • To avoid unnecessary backtracking • The cut is an explicit control mechanism. The ordering of clauses and goals within clauses is less explicit but just as important • But you also need to think about the logic! formal models of computation