10.3 Logic

advertisement
Prolog: Control, Negation and Cut
•
•
•
•
Backtracking
The cut operator !
Negation-as-Failure
not
1
Backtracking and Nondeterminism
member(X, [X|_]). % _ is an anonymous variable
member(X, [_|T]) :- member(X, T).
?- member(fred, [john, fred, paul, fred]).
yes
Deterministic query
?- member(X, [john, fred, paul, fred]).
X = john;
X = fred;
Nondeterministic query
X = paul;
X = fred;
no
2
The Problem of Controlling Backtracking
color(cherry, red).
color(banana, yellow).
color(apple, red).
color(apple, green).
color(orange, orange).
color(X, unknown).
?- color(banana, X).
X = yellow
?- color(physalis, X).
X = unknown
?- color(cherry, X).
X = red;
X = unknown;
no
3
The CUT
• The cut is a built-in predicate written as !
• The cut always succeeds
• When backtracking over a cut, the goal that
caused the current procedure to be used fails
• Not used for its logical properties, but
to control backtracking.
4
How CUT works
• Suppose goal H is called, and has two clauses:
H1 :- B1,… Bi, !, Bk,… Bm.
H2 :- Bn,… Bp.
• If H1 matches goals B1...Bi are attempted and
may backtrack among themselves
• If B1 fails, H2 will be attempted
• But as soon as ! is crossed, Prolog commits to
the current choice.
All other choices are discarded.
5
Commitment to the Clause
H1 :- B1,… Bi, !, Bk,… Bm.
H2 :- Bn,… Bp.
• Goals Bk...Bm may backtrack amongst
themselves, but
• If goal Bk fails, then the predicate fails and the
subsequent clauses are not matched
6
How to Remember Cut
• Think of the ‘cut’ as a ‘fence’
– When crossed it always succeeds,
– And asserts a commitment to the current solution.
• When a failure tries to cross the fence, the entire goal is
failed.
commit
H1 :- B1, B2, ..., Bi, !, Bk, ..., Bm.
fail calling goal
7
Uses of Cut: Deterministic Predicates
1. To define a deterministic (functional) predicate.
Example: a deterministic version of member, which is
more efficient for doing ‘member checking’ because it
doesn't need to give multiple solutions:
membercheck(X, [X|_]) :- !.
membercheck(X, [_|L]) :- membercheck(X, L).
?- membercheck(fred, [joe, john, fred, paul]).
yes.
?- membercheck(X, [a, b, c]).
X = a;
no.
8
Uses of Cut: Specify Exclusion Cases
2. To specify exclusion of cases by ‘committing’ to the
current choice.
Example: max(X, Y, Z) makes Z maximum of X and Y
max(X, Y, X) :- X >= Y.
max(X, Y, Y) :- X < Y.
Note: Both clauses are logically correct statement
Using cut can get rid of the second test:
max(X, Y, X) :- X >= Y, !.
max(X, Y, Y).
9
Max with Cut
max(X, Y, X) :- X >= Y, !.
max(X, Y, Y).
If max is called with X ≥ Y, the 1st clause succeeds, and
because of ! the 2nd clause isn't called
• The advantage
– The test isn't made twice if X<Y
• The disadvantage
– Each rule isn't a logically correct statement about the predicate.
– To see why this is a problem, try
?- max(10, 0, 0).
10
Max with Cut
• It is better to use ! and both tests:
max(X, Y, X) :- X >= Y, !.
max(X, Y, Y) :- X < Y.
• This backtracks correctly and avoids unnecessary tests
• Or, if order of clause may change:
max(X, Y, X) :- X >= Y, !
max(X, Y, Y) :- X < Y, !.
11
A Larger Example
• Several versions of the disjoint partial map split:
split(list of integers, non-negatives, negatives).
1. Without cut:
split([], [], []).
split([H|T], [H|Z], R) :- H >= 0, split(T, Z, R).
split([H|T], R, [H|Z]) :- H < 0, split(T, R, Z).
– Good code
•
•
Each clause stands on its own as a logical statement
Inefficient because choice points are retained.
–
Only the 1st solution is needed, but we don't ignore backtracked
solutions
12
A Larger Example (cont.)
2. A version using cut. Most efficient, but not the best
‘defensively written’ code. The third clause does not stand
on its own as a fact about the problem. As in normal
programming languages, it needs to be read in context.
This style is often seen in practice, but is discouraged.
split([], [], []).
split([H|T], [H|Z], R) :- H >= 0, !, split(T, Z, R).
split([H|T], R, [H|Z]) :- split(T, R, Z).
• Minor modifications may have unintended effects
– E.g., adding more clauses
• Backtracked solutions are invalid
13
A Larger Example (cont. 2)
3. A version using cut which is also ‘safe’. The only
inefficiency is that the goal H < 0 will be executed
unnecessarily whenever H < 0
split([], [], []).
split([H|T], [H|Z], R) :- H >= 0, !, split(T, Z, R).
split([H|T], R, [H|Z]) :- H < 0, split(T, R, Z).
• Recommended for practical use.
• Hidden problem: the third clause does not capture the
idea that H < 0 is a committal. Here committal is the
default because H < 0 is in the last clause.
• Some new compilers detect that H < 0 redundant.
14
A Larger Example (cont. 3)
3. A version with unnecessary cuts
split([], [], []) :- !.
split([H|T], [H|Z], R) :- H >= 0, !, split(T, Z, R).
split([H|T], R, [H|Z]) :- H < 0, !, split(T, R, Z).
• First cut is unnecessary because anything matching first
clause will not match anything else anyway
– Most Prolog compilers can detect this
• Why is the third cut unnecessary? Because H < 0 is in
the last clause. Whether or not H < 0 fails, there are no
choices left for the caller of split. However, the above will
work for any order of clauses.
15
Mapping with Cut
• Remember squint? Maps integers to their squares,
map anything else to itself:
squint([], []).
squint([X|T], [Y|L]) :integer(X), !, Y is X * X, squint(T, L).
squint([X|T], [X|L]) :- squint(T, L).
16
More Mapping with Cut
Extract the even numbers...
evens([], []).
evens([X|T], [X|L]) :- 0 is X mod 2, !, evens(T, L).
evens([X|T], L) :- evens(T, L).
Extract the unique members...
uniques([], []).
uniques([X|T], L) :- member(X, T), !, uniques(T,L).
uniques([X|T], [X|L]) :- uniques(T,L).
17
Negation-as-Failure
• Using cut together with the built-in predicate fail
defines a kind of negation.
• Examples:
• Mary likes any animals except reptiles:
likes(mary,X) :- reptile(X), !, fail.
likes(mary,X) :- animal(X).
• A utility predicate meaning something like “not equals”:
different(X, X) :- !, fail.
different(_, _).
18
Negation-as-Failure: not
• We can use the idea of “cut fail” to define the predicate
not, which takes a term as an argument
• not “calls” the term, evaluating as if it was a goal:
–
–
not(G) fails if G succeeds
not(G) succeeds if G does not succeed.
• In Prolog,
not(G) :- call(G), !, fail.
not(_).
• call is a built-in predicate.
19
Negation-as-Failure: not (cont.)
• Most Prolog systems have a built-in predicate not.
SICStus Prolog calls it \+.
• not does not correspond to logical negation, because it
is based on the success/failure of goals.
• It can, however, be useful
likes(mary, X) :- not(reptile(X)).
different(X, Y) :- not(X = Y).
20
Misleading Negation-as-Failure
• A student who missed some Prolog lectures was commissioned to
write a Police Database system in Prolog
• The database held the names of members of the public, marked by
whether they are innocent or guilty of some offence
• Suppose the database contains the following:
innocent(peter_pan).
innocent(X) :- occupation(X, nun).
innocent(winnie_the_pooh).
innocent(julie_andrews)
guilty(X) :- occupation(X, thief).
guilty(joe_bloggs).
guilty(rolf_harris).
• Consider the following dialogue:
?- innocent(st_francis).
no.
21
Problem – No may not mean False
• This can't be right – we know that St. Francis is innocent
• Why does this happen?
• Prolog produces no, because st_francis is not in the
database
• The user will believe it because the computer says so
and the database is hidden from the user
• How to solve this?
22
not Makes Things Worse
• Using not doesn't help
guilty(X) :- not(innocent(X)).
• This makes matters even worse
?- guilty(st_francis).
yes
• It is one thing to show that st_francis cannot be
demonstrated to be innocent, but
it is very bad to incorrectly show that he is guilty
23
Negation-as-Failure can be Non-Logical
• More subtle than the innocent/guilty problem, not can
lead to some extremely obscure programming errors.
• An example using a restaurant database
good_standard(goedels).
good_standard(hilberts).
expensive(goedels).
reasonable(R) :- not(expensive(R)).
• Consider query:
?- good_standard(X), reasonable(X).
X = hilberts
• But let's ask the logically equivalent question:
?- reasonable(X), good_standard(X).
no.
24
Why Different Answers?
• Why different answers for logically equivalent queries?
?- good_standard(X), reasonable(X).
?- reasonable(X), good_standard(X).
• In the 1st query, X is always instantiated when
reasonable(X) is executed
• In the 2nd query, X is not instantiated when
reasonable(X) is executed
• The semantics of reasonable(X) differ depending on
whether its argument is instantiated!
25
Bad Programming Practice
• It is bad to write programs that destroy the
correspondence between the logical and procedural
meaning of a program without any good reason
• Negation-as-failure does not correspond to logical
negation, and so requires special care
26
How to fix it?
• One way is to specify that
– Negation of a non-ground formula is undefined
• A formula is ground if is has no unbound variables
• Some Prolog systems issue a run-time exception when
a non-ground goal is negated
27
Download