One misapprehension about Qi is
that it does not or cannot use S-expression syntax and
hence, unlike Lisp, Qi is not a good
metaprogramming language. It seems a good time to lay
this to rest. Qi is a very powerful
metaprogramming language. Qi does have S-expression
syntax, although as user you don't generally use it.
So
(define foo
[a] -> b)
is short for
(define foo
(cons a []) -> b)
for
(define foo
(cons a ()) -> b)
or
(define foo
(cons a NIL) -> b)
You get the idea. You can write Qi
in a 'Lispy' syntax.
If you want to see your Qi program in
S-expression syntax you can do it by reading your program
file through the Qi reader without loading the
file. The 1-place Qi function read-file does
that - it reads the file through the Qi reader
and converts it to Lispy form. Try (read-file "Put
your Qi file name here") to see this.
If you want to see the Lisp function corresponding to
your Qi function then (ps <function>) will
do the trick. If there is no definition then [] will be
returned.
Here are two examples of the use of these facilities.
Generating a
Rewrite Engine from a Confluent and Terminating Set of
Equations
Lets assume that you've generated a confluent and
terminating set of equations from a Knuth-Bendix program
and you want to turn your set E of equations into a
rewrite engine. We'll assume that the equations are
represented as two element lists; so [a b] represents the
equation a = b. This program will do it.
(define make-rewrite-engine
E -> [define rewrite | (append (mapcan
make-rewrite-rule E) (default))])
(define mapcan
_ [] -> []
F [X | Y] -> (append (F X) (mapcan F Y)))
(define make-rewrite-rule
[X Y] -> [(cons-form X) -> [rewrite (cons-form
Y)]])
(define cons-form
[X | Y] -> [cons (cons-form X) (cons-form Y)]
X -> X)
(define default
-> [[cons X Y] -> [map rewrite [cons X Y]]
X -> X])
So if I try this
(make-rewrite-engine [[a b] [b
c]])
I get
[define rewrite
a -> [rewrite b]
b -> [rewrite c]
[cons X Y] -> [map rewrite [cons X Y]]
[X -> X]]
Ok so far; however this is just
a list, not a function. But Qi has its own
version of eval which works like this.
The normal form of (eval e) is
the normal form of e* where e* results from e by the
uniform substitution of round brackets for square ones.
e.g. (eval [+ 2 3]) = (+ 2 3) =
5
So to generate the function we
make one small change.
(define make-rewrite-engine
E -> (eval [define rewrite | (append (mapcan
make-rewrite-rule E) (default))]))
we define a function normalise
that rewrites an input to a fixpoint (fix is a
higher-order Qi function that does that)
(define normalise
X -> (fix rewrite X))
and our top level that tests for
equality w.r.t. the equations.
(define equal?
X Y -> (= (normalise X) (normalise Y)))
Quick test
(77-) (equal? [f a] [f b])
true
Partial
Evaluation
Lisp is a great language for doing partial evaluation
because unfolding is so simple; effectively unfolding is
a kind of beta-reduction using the lambda body of the
defined function. Because Lisp is so simple
syntactically, its a lovely (object) language to reason
about.
But it does not follow that it is necessarily the best
(meta) language to reason with. Partial evaluation is a
case in point. It is very useful to have pattern-matching
when reasoning over Lisp functions. Using Qi as
the metalanguage and Lisp as the object language gives
you the best of both worlds.
Ok, lets have an example. We define power in Qi.
(define power
_ 0 -> 1
X Y -> (* X (power X (- Y 1))))
(ps power) gives
[DEFUN power [V16 V17]
[COND [[EQL 0 V17] 1] [T [THE NUMBER [* V16 [power V16
[1- V17]]]]]]]
We want to partially evaluate
[power X 3] where the variable X signifies the unknown.
My partial evaluator is greatly simplified because
the purpose of this piece is to show you how to do it and
not to do it myself (which would take up too much space
and take away your fun). Here it is.
(define partially-evaluate
[X | Y] -> (let Z (simplify (unfold [X | Y]))
(if (= [X | Y] Z)
(map partially-evaluate Z)
(partially-evaluate Z)))
X -> X)
Unfolding uses the Lisp
definition.
(define unfold
[F | X] -> (let Body (lambda-body (def F))
(if (empty? Body)
[F | X]
(beta-reduce Body X)))
X -> X)
def gets the definition;
(define def
F -> (get-prop F qi::source_code []))
(define lambda-body
[] -> []
[Defun F Params Body] -> [lambda Params Body])
Here is a lazy man's beta
reduction which ignores variable clashes
(define beta-reduce
[lambda [] Body] [] -> Body
[lambda [V | Vs] Body] [X | Y] -> (beta-reduce [lambda
Vs (subst X V Body)] Y))
simplify is not too hard - the
only thing to note is that Lisp-in-Qi uses
uppercase for all the Lisp system functions and Qi
uses uppercase for variables (except NIL which is still
the empty list). So if we write a function test-cond that
returns true if the input is COND like this
(define test-cond
COND -> true
_ -> false)
it will not work - it will
return 'true' for every input. The correct way is.
(define test-cond
X -> true where (= X COND)
_ - > false)
Once that's clear the rest is
easy.
(define simplify
[Cond [NIL _] | Code] -> [Cond | Code] where (= Cond
COND)
[Cond [True X] | _] -> X where (and (= Cond COND) (=
True T))
[Eql X X] -> T where (= EQL Eql)
[Eql X Y] -> NIL where (and (number? X) (number? Y))
[1- X] -> (- X 1) where (number? X)
[* X 1] -> X
[X | Y] -> (map simplify [X | Y])
X -> X)
So if I type in
(partially-evaluate [power X 3])
I get
[THE NUMBER [* X [THE NUMBER [*
X [THE NUMBER X]]]]]
which is correct.
Mark
Copyright
(c) 2008, Mark Tarver
|