Laws of Form

Kragen Sitaker kragen@pobox.com
Tue, 16 Mar 1999 13:08:09 -0500 (EST)


A few simple Scheme programs to create and evaluate Laws of Form
expressions.

By the way, in the original text, (expr) was represented as something like

____
    |
expr|

; evaluate Laws of Form expressions.
; Kragen Sitaker, 1999-03-16, for fun
; A cross is a list.
; The whole expression is a list too, which is confusing.
; so the empty expression is (  );
; the expression of one cross is ( () );
; the expression of two adjacent crosses is ( ()() );
; the expression of one cross inside another is ( (()) ).
;
; There are two basic reductions: ( (()) ) -> (  ), and ( ()() ) -> ( () ).
;
; With those two basic reductions, you can reduce any expression to either
; (  ) or ( () ).
;
; So ( ((())) ) becomes ( () ) by the first reduction. ( ((()))() ) becomes
; ( ()() ) by the first reduction, then ( () ) by the second reduction.
;
; I will do the simple thing and express the values internally as #t or #f.
; #t will represent ( () ), and #f will represent (  ).

; If none of this makes any sense to you, it may be because you
; haven't read the book (_Laws of Form_).  Briefly, it's about a very
; simple formalism for Boolean logic, written by a brilliant guy who
; (later?) became a nutcase.

; Essentially, a "cross" is like a NAND or a NOR (depending on which
; interpretation you choose; by the interpretation I'm using internally,
; it's a NOR.)  Laws of Form expressions are very easy to evaluate by
; hand; since the representation for one of the two values is the null
; string, then a subexpression that evaluates to that value simply 
; disappears from the expression.  No need for the 1 AND X -> X 
; transformation; it happens automatically as a consequence of the notation.

(define (lof-eval-helper expr)
  (cond ((null? expr)          #f)
	((lof-eval (cdr expr)) #t)
	(#t                    (not (lof-eval (car expr))))
  )
)

(define (lof-eval expr)
  (if (lof-eval-helper expr) '(()) '())
)

; This transforms an ordinary Boolean expression into LOF-notation.
; Again, #f becomes the empty string, and #t becomes a single cross.
; Grammar:
; expr -> #f | #t | (and expr expr) | (or expr expr) | (not expr) | symbol
; This really feels quite crude, and it's sort of voodooish.  It uses the
; opposite internal representation of true and false that lof-eval-helper
; does.  (I realized this after I finished writing it, which is why I think
; it's kind of voodooish.)  It ends up working the same way because everything
; gets wrapped in one fewer set of parentheses.

(define (lof-transform expr)

  (define (lof-and args)
    (if (null? (cddr args))
	(cons (cons (lof-transform (car args))
		    (cons (lof-transform (cadr args)) '())
	      )
	)
	(error 'and-got-wrong-number-of-args (cons 'and args))
    )
  )

  (define (lof-or args)
    (if (null? (cddr args))
	(cons (cons (lof-transform (car args)) '())
	      (cons (cons (lof-transform (cadr args)) '()) '())
        )
	(error 'or-got-wrong-number-of-args (cons 'or args))
    )
  )

  (define (lof-not args)
    (if (null? (cdr args))
	(cons (lof-transform (car args)) '())
	(error 'not-got-wrong-number-of-args (cons 'not args))
    )
  )

  (cond ((eq? expr '#f) '())
	((eq? expr '#t) '(()))
	((pair? expr)
	 (let ((head (car expr)))
	   (cond ((eq? head 'and) (lof-and (cdr expr)))
		 ((eq? head 'or)  (lof-or (cdr expr)))
		 ((eq? head 'not) (lof-not (cdr expr)))
		 (#t (error 'unimplemented-operator head))
	   )
         )
	)
	((symbol? expr) expr)
	(#t (error 'invalid-boolean-expression expr))
  )
)

; In general, (lof-eval-helper (lof-transform expr)) should give you the
; boolean evaluation of 'expr'.  (As long as it doesn't contain any
; symbols.)

; lof-transform can produce expressions with symbols in them.
; Here is a symbol-substitution thingy to go through and substitute
; expressions for the symbols.

(define (lof-substitute expr alist)
  (cond ((null? expr) '())
	((pair? expr) (cons (lof-substitute (car expr) alist) 
			 (lof-substitute (cdr expr) alist)
		   )
	)
	((symbol? expr)
	 (let ((replpair (assq expr alist)))
	   (if replpair (cdr replpair) expr)
	 )
	)
	(#t (error 'invalid-boolean-expression expr))
  )
)

; OK, so now that we have expressions with variables, we can talk about
; simplifying them.  (Expressions without variables, of course, just simplify
; to 'true' or 'false'.)
; There are several basic simplifications:
; ((expr)) -> expr
; (() expr) -> (())
; ((()) expr) -> (expr)
; (a b) -> (b a)
; 
; And, of course, subexpressions without variables in them can be simplified by
; means of lof-eval.
;
; I'm not sure how to implement these, though.  And they're not sufficient, anyway.
; Try (lof-transform '(or a (not a))) -- the result should simplify to (()), but
; I don't see an obvious way to do it.  And (lof-transform '(or (and a b) 
; (and (not a) b))) should simplify to b.  I don't see any kind of simple way to
; do this.
;
; This is sort of silly, since the whole rationale for doing this was to see how
; to simplify Boolean expressions expressed as LOF expressions.
;
; Of course, determining whether a particular Boolean expression simplifies to
; #f is the SAT problem, so I shouldn't expect a simple, easy solution here.