Tuesday, May 16, 2006

Qi Macros and Type Patterns

Taking a step back to the Magic Prime Type, we find that the pattern that we used to generate the prime type may be useful to encapsulate in some manner. The pattern goes as follows: If you have a base class like number, and you have a derived class such as prime, then you can create these "prime like" classes as long as you have a function that returns true for the basic and derived type. Simply put, if we could just swap different lexical elements into our prime datatype constructor, we could have a useful piece of code for creating exotic types such as the “even” type or perhaps the “catalan” type. Obviously, when we talk about placing lexical elements inside of code, we are talking macros!

So what would the Lisp-like macro look like for our “subtype-with-test” function? Something like the following, only better.

(DEFMACRO subtype-with-test ( BaseType NewType BaseTest NewTest)
`(datatype ,NewType
if(and ( ,BaseTest X ) ( ,NewTest X ) )
____
X : ,NewType ;

___
( ,NewTest X) : verified >> X : ,NewType ;

( subtype B A ) ; X : B ;
___
X : A;

___
(subtype ,NewType ,BaseType);
)
)

However, if you try to input that into Qi, you’ll find it will not make a compiling as something seems to happen to the ;. We need to use the "macroexpand" function mentioned in Appendix B of FPIQ language book. Fortunately for us, the syntax makes our final Qi macro a bit easier to read than the lisp version. We first create a pattern that looks like a function call that matches our macro. We’ll name it the same as above and it will take the same arguments.

(define macroexpand
[subtype-with-test BaseType NewType BaseTest NewTest] ->
[datatype NewType
if[and [ BaseTest X ]
[ NewTest X ]]
______
X : NewType ;

____
[ NewTest X ] : verified >> X : NewType ;

[ subtype B A ] ; X : B ;
___
X : A;

___
[ subtype NewType BaseType ]; ]
X -> X
)

And that’s all there is to it! Just remember to make sure to include the X-> X pattern at the bottom. Every single token receives a macroexpand so we need to ensure that it works as intended for all but our special "subtype-with-test" macro. Note that the pattern match serves as our macro definition. We define it in no other place.

Now doing the following creates a brand new type!

(subtype-with-test number prime integer? prime?)

We should note that the macroexpanded "subtype-with-test" prevents the evaluation of the 4 inputs. So we find the typical problem with variable capture. We don’t have the ability to name our classes X, A, or B!!! *laugh* Not a big loss, but we can modify the code and learn to deal with variable capture. We do this in the same way we deal with it in Lisp, namely gensyms.

define macroexpand
[subtype-with-test BaseType NewType BaseTest NewTest] ->
(let X (gensym "Temp")
(let A (gensym "Temp")
(let B (gensym "Temp")
[datatype NewType
if[and [ BaseTest X ]
[ NewTest X ]]
______
X : NewType ;

____
[ NewTest X ] : verified >> X : NewType ;

[ subtype B A ] ; X : B ;
___
X : A;

___
[ subtype NewType BaseType ]; ] )))

X -> X
)

One important point to note is that the "Temp" (or whatevever you want to name your gensym) must start in UPPER CASE! Otherwise, Qi won’t be able to tell it is a variable!

Qi does warn about these sorts of issues however, if you see the free variable warning in a macro, capture them with gensyms to turn this sort of output …

(0-) (load "test/pmacro2.qi")
======> Warning:
the following variables are free in macroexpand: B; X; A;


WARNING:
DEFUN/DEFMACRO: redefining function macroexpand in D:\tools\Qi 6.2\startup_qi.tx
t, was defined in D:\tools\Qi 6.2\Install Qi\Qi 6.3.txtmacroexpand

Real time: 0.0156303 sec.
Run time: 0.015625 sec.
Space: 185740 Bytes
loaded


into this much nicer output…

(0-) (load "test/pmacro3.qi")

WARNING:
DEFUN/DEFMACRO: redefining function macroexpand in D:\tools\Qi 6.2\startup_qi.tx
t, was defined in D:\tools\Qi 6.2\Install Qi\Qi 6.3.txtmacroexpand

Real time: 0.0312606 sec.
Run time: 0.03125 sec.
Space: 194036 Bytes
Loaded

Note the lack of compiler warnings about free variables. However it will always warn you about redefining macroexpand. Of course, since we are defining VARIABLES in the datatype, no real variable capture can happen. Our macro requires lower case symbols to even operate. But this example shows how to avoid the issue in the odd case where you want to make a macro that you can define different variable names inside it. (Can anyone think of a good reason to do this?)

So let us try this new macro out! We’ll create an even test and create a new type called "even" that can only type to actual even numbers.


(12+) (define even?
{ number --> boolean }
X -> (integer? (/ X 2)) )
even? : (number --> boolean)

(13+) (subtype-with-test number even integer? even?)
even : unit

(14+) 3 : even
error: type error

(15+) 4 : even
4 : even

(16+) 100 : even
100 : even

(17+) 101 : even
error: type error


I’d say that is an impressive job for one line of code! Let me know what you think!

0 Comments:

Post a Comment