Homework 6: Symbolic Evaluation of Boolean Expressions
Due: Friday, Feburary 26, 2010
Extra Credit
Overview
Write a Scheme function reduce
that reduces boolean expressions (represented in Scheme notation) to simplified form. For the purposes of this assignment, boolean expressions are Scheme expressions constructed from:
- the boolean constants
true
andfalse
; - boolean variables (represented by symbols other than
true
,false
,not
,and
,or
,implies
, andif
) that can be bound to eithertrue
orfalse
. - the unary operator
not
. - the binary operators
and
,or
, andimplies
, and - the ternary operator
if
.
The course staff is providing function parse
and unparse
(in the file parser) that convert boolean expressions in Scheme notation to a simple inductively defined type called boolExp
and vice-versa. The coding of parse
and unparse
is not difficult, but it is tedious (like most parsing) so the course staff is providing this code rather than asking students to write it.
These parsing functions rely on the following Scheme data definitions:
Given
(define-struct Not (arg)) (define-struct And (left right)) (define-struct Or (left right)) (define-struct Implies (left right)) (define-struct If (test conseq alt))
a boolExp
is either:
- a boolean constant
true
andfalse
; - a symbol
S
representing a boolean variable; (make-Not X)
whereX
is aboolExp
;(make-And X Y) where X
andY
are {{boolExp}}s;(make-Or X Y) where X
andY
are {{boolExp}}s;(make-Implies X Y) where {{X
andY
are {{boolExp}}s; or(make-If X Y Z)
whereX
,Y
, andZ
are {{boolExp}}s.
A bool-SchemeExp is either:
- a boolean constant
true
orfalse
; - a symbol
S
; (list 'not X)
whereX
is abool-SchemeExp
;(list op X Y)
whereop
is'and
,'or
, or'implies
whereX
andY
are {{bool-SchemeExp}}s;(list 'if X Y Z)
whereX
,Y
, andZ
are {{bool-SchemeExp}}s.
The provided functions parse
and unparse
have the following signatures.
parse: bool-SchemeExp -> boolExp unparse: boolExp -> bool-SchemeExp
Given a parsed input of type boolExp
, the simplification process
consists of following four phases:
- Conversion to
if
form implemented by the functionconvert-to-if
. - Normalization implemented by the function
normalize
. - Symbolic evaluation implemented by the function
eval
. - Conversion back to conventional boolean form implemented by the function
convert-to-bool
.
A description of each of these phases follows. The reduce
function has type bool-SchemeExp -> bool-SchemeExp
.
Conversion to if
form
A boolean expression can be converted to if
form by repeatedly
applying the following
rewrite rules in any order until no rule is applicable.
(make-Not X) => (make-If X false true) (make-And X Y) => (make-If X Y false) (make-Or X Y) => (make-If X true Y) (make-Implies X Y) => (make-If X Y true)
The conversion process always terminates (since each rewrite strictly
reduces the number of logical connectives in the expression) and yields
a unique answer independent of the order in which the rewrites are performed.
This property is called the Church-Rosser property, after the logicians
(Alonzo Church and Barkley Rosser) who invented the concept.
Since the reduction rules for this phase are Church-Rosser, you can write the function convert-to-if
using simple structural recursion. For each of the boolean operators And
, Or
, Not
, and Implies
, reduce the component expressions first and then applying the matching reduction (except for if
for which there is no top-level reduction).
The following examples illustrate the conversion process:
(check-expect (convert-to-if (make-Or (make-And 'x 'y) 'z)) (make-If (make-If 'x 'y false) true 'z)) (check-expect (convert-to-if (make-Implies 'x (make-Not 'y)) (make-If 'x (make-If 'y false true) true))
We suggest simply traversing the tree using the structural recursion template for type boolExp
and converting all structures (other than if}}s) to the corresponding {{if
structures.
Write an inductive data definition and template for boolean formulas in if
form, naming this type ifExp
. (Note: make-If
is the only constructor, other than variables and constants for ifExp
a {{boolExp}} is either: * a boolean value {{true}} and {{false}}; * a symbol {{s}} representing a boolean variable; * {{(make-Not M)}} where {{M}} is a {{boolExp}}; * {{(make-And M N)}} where {{M}} and {{N}} are {{boolExps}}; * {{(make-Or M N)}} where {{M}} and {{N}} are {{boolExps}}; * =(make-Implies M N) where =M= and =N= are =boolExps=; or * =(make-If M N P)= where =M=, =N=, and =P= are =boolExps=. The provided function {{parse: input -> boolExp}} takes a Scheme expression and returns the corresponding {{boolExp}}. ----+Normalization An =ifExp= is _normalized_ iff every sub-expression in =test= position is either a variable (symbol) or a constant (=true= or =false=). We call this type =norm-ifExp=. For example, the =ifExp=
(make-If (make-If X Y Z) U V))
is not a =norm-ifExp= because it has an =If= construction in test position. In contrast, the equivalent =ifExp=
(make-If X (make-If Y U V) (make-If Z U V))
is normalized and hence is an =norm-ifExp=. The normalization process, implemented by the function =normalize: ifExp -> norm-ifExp= eliminates all =if= constructions that appear in the _test_ position of other =if= constructions. We perform this transformation by repeatedly applying the following rewrite rule (to any portion of the expression) until it is inapplicable:
(make-If (make-If X Y Z) U V) => (make-If X (make-If Y U V) (make-If Z U V)).
This transformation always terminates and yields a unique answer independent of the order in which rewrites are performed. The proof of this fact is left as an optional exercise. In the =normalize= function, it is critically important not to duplicate any work, so the order in which reductions are made really matters. Do *NOT* apply the normalization rule above unless =U= and =V= are already normalized, because the rule duplicates both =U= and =V=. If you reduce the _consequent_ and the _alternative_ (=U= and =V= in the left hand side of the rule above) before reducing the _test_, =normalize= runs in linear time (in the number of nodes in the input); if done in the wrong order it runs in exponential time in the worst case. (And some of our test cases will exhibit this worst case behavior.) Hint: define a sub-function head-normalize that takes three =norm-ifExp=s =X=, =Y=, and =Z= and constructs a =norm-ifExp= equivalent to =(makeIf X Y Z)=. This help function processes =X= because the =test= position must be a variable or a constant, yet =X= can be an arbitrary =norm-ifExp=. In contrast, =(head-normalize X Y Z)= never even inspects =Y= and =Z= because they are already normalized and the normalizing transformations performed in =head-normalize= never place these expressions in =test= position. The following examples illustrate how the =normalize= and =head-normalize= functions behave:
(check-expect (head-normalize 'x 'y 'z) (make-If 'x 'y 'z))
(check-expect (head-normalize true 'y 'z) (make-If true 'y 'z))
(check-expect (head-normalize false 'y 'z) (make-If false 'y 'z))
(check-expect (head-normalize (make-If 'x 'y 'z) 'u 'v) (make-If 'x (make-If 'y 'u 'v) (make-If 'z 'u 'v)))
(check-expect (head-normalize (make-If 'x (make-If 'yt 'yc 'ya) (make-If 'zt 'zc 'za)) 'u 'v)
(make-If 'x (make-If 'yt (make-If 'yc 'u 'v) (make-If 'ya 'u 'v)) (make-If 'zt (make-If 'zc 'u 'v) (make-If 'za 'u 'v))))
(check-expect (normalize true) true)
(check-expect (normalize false) false)
(check-expect (normalize 'x) 'x)
(check-expect (normalize (make-If 'x 'y 'z)) (make-If 'x 'y 'z))
(check-expect (normalize (make-If (make-If 'x 'y 'z) 'u 'v)) (make-If 'x (make-If 'y 'u 'v) (make-If 'z 'u 'v)))
Once a large formula has been normalized, do not try to print it unless you know that the formula is small! The printed form can be exponentially larger than the internal representation (because the internal representation can share subtrees). Before you start writing =normalize=, write the template corresponding to the inductive data definition of =norm-ifExp=. ----+Symbolic Evaluation The symbolic evaluation process, implemented by the function =eval: norm-if-form environment -> norm-if-form=, reduces a =norm-if-form= to simple form. In particular, it reduces all tautologies (expressions that are always true) to =true= and all contradictions (expressions that are always false) to =false=. <p> Symbolic evaluation applies the following rewrite rules to an expression until none is applicable (with one exception discussed below):
(make-If true X Y) => X
(make-If false X Y) => Y
(make-If X true false) => X
(make-If X Y Y) => Y
(make-If X Y Z) => (make-If X Y[X <- true] Z[X <- false])
The notation =M[X <- N]= means =M= with all occurrences of the symbol =X= replaced by the expression =N=. It is very costly to actually perform these subtitutions on =norm-if-form= data. To avoid this computational expense, we simply maintain a list of bindings which are pairs consisting of symbols (variable names) and boolean values { =true=, =false= }. The following data definition definition formally defines the =binding= type. A =binding= is a pair (make-binding s v) where s is a symbol (a variable) and v is a boolean value (an element of { =true=, =false= }. An =environment= is a =(listOf binding)=. When the =eval= function encounters a variable (symbol), it looks up the symbol in the environment and replaces the symbol it's boolean value if it exists. <p> These rewrite rules do not have the Church-Rosser property. The last two rewrite rules are the spoilers; the relative order in which they are applied can affect the result in some cases. However, the rewrite rules do have the Church-Rosser property on expressions which are tautologies or contradictions. <p> If the final rule is applied only when =X= actually occurs in either =Y= or =Z=, then the symbolic evaluation process is guaranteed to terminate. In this case, every rule either reduces the size of the expression or the number of variable occurrences in it. We recommend applying the rules in the order shown from the top down until no more reductions are possible (using the constraint on the final rule). Note that the last rule should only be applied once to a given sub-expression. ----+Conversion to Boolean Form The final phase converts an expression in (not necessarily reduced) =If= form to an equivalent expression constructed from variables and { =true=, =false=, =And=, =Or=, =Not=, =Implies=, =If=}. This process eliminates every expression of the form
(make-If X Y Z)
where one of the arguments {=X=, =Y=, =Z=} is a constant { =true=, =false=}. Use the following set of reduction rules to perform this conversion
(make-If X false true) => (make-Not X)
(make-If X Y false) => (make-And X Y)
(make-If X true Y) => (make-Or X Y)
(make-If X Y true) => (make-Implies X Y)
YYYcodeYYY
where X
, Y
, and Z
are arbitrary If
forms.
This set of rules is Church-Rosser, so the rules can safely be applied
using simple structural recursion.