I was about to add some new features to my n-dimensional raytracer when I realized that I was going to be jumping through a ton of hoops that one needn't jump through (or, more precisely, that Lisp could jump through for me). So, I started tinkering with doing optimized vector math.
In order to keep my options open about which type of numbers to use, I tried this sort of stuff for quite awhile:
(defconstant float-type 'double-float) (defconstant vector-type `(vector ,float-type))
(defparameter) all ran into troubles. Lots of places
that I tried to use them,
sbcl was yelling at me that
they were undefined variables. It wanted their values at compile time,
but it wasn't assigning them values until run time. Fair enough.
So, I finally just looked in the
Index for all of the
(def...) macros in search of one
that would be in effect at compile time. Lo and behold, there was
(deftype). Not only would it be effective when I wanted it
to be, it would actually work as a type! (Okay, small victory.)
I should have searched through the CLHS sooner, but it's horrendously formatted and something there tries to make a connection back to my browser from time to time.
Anyhow, rather than littering my code with stuff like:
(declare (type vector-type v1 v2))
I was hoping to do the following:
(defmacro declf (&rest vars) "declare something as being a float" `(declare (type float-type ,@vars))) (defmacro declv (&rest vars) "declare something as being a vector" `(declare (type vector-type ,@vars))) (defmacro thef (&rest what) "declare something as resulting in a float" `(the float-type ,@what)) (defun vector-scale (alpha vec) (declf alpha) (declv vec) (map 'vector-type #'(lambda (x) (declf x) (thef (* x alpha))) vec))
But, I'm getting no love. (Actually, I think the
is working fine.) You see,
sbcl has already forgotten about
the possibility of
(declare) directives by the time
it is done expanding the macro. So, it thinks I'm trying to call
(type) using some variable
and passing the result to some function
Is there some other
(def...) I'm missing that might
come in handy here? Should I just use
(defmethod) and make it search every time from my
list of one method for each generic? Or, should I make some nasty
macro for making functions with typed arguments?
(defun proper-list-p (x) (if (consp x) (proper-list-p (cdr x)) (not x))) (defun prep-typed-vars-and-decls (arg-forms) (let (vars decls) (dolist (form arg-forms) (cond ;; raw symbol; x --or-- &key ;; or normal list: (x default-x) ((or (atom form) (proper-list-p form)) (push form vars)) ;; dotted-entry: (x . type) ;; or : ((x default-x) . type) (t (let ((var (car form)) (type (cdr form))) (push var vars) (if (consp var) (push `(declare (type ,type ,(car var))) decls) (push `(declare (type ,type ,var)) decls)))))) (values (nreverse vars) decls))) (defun with-argtypes (pre-args arg-forms post-args body) (multiple-value-bind (vars decls) (prep-typed-vars-and-decls arg-forms) `(,@pre-args (,@vars) ,@post-args ,@decls ,@body))) (defmacro defun-at (name arg-forms &body body) (with-argtypes `(defun ,name) arg-forms nil body))
This allows me to do pretty much everything with some special syntax for throwing types onto things.... What used to be a variable form can now be a variable form or a dotted pair of variable-form + type...
CL-USER(31): (macroexpand-1 '(defun-at aa ( (x . integer) &key ((y 0) . integer) (z 1)) (values x y z))) (DEFUN AA (X &KEY (Y 0) (Z 1)) (DECLARE (TYPE INTEGER Y)) (DECLARE (TYPE INTEGER X)) (VALUES X Y Z)) T
But, then I'd still need a whole slew of other definitions:
(defmacro lambda-at (arg-forms &body body) (with-argtypes '(lambda) arg-forms nil body)) (defmacro let-at (arg-forms &body body) (with-argtypes '(let) arg-forms nil body)) (defmacro let-at* (arg-forms &body body) (with-argtypes '(let*) arg-forms nil body)) (defmacro do-at (arg-forms end-forms &body body) (with-argtypes '(do) arg-forms end-forms body)) (defmacro do-at* (arg-forms end-forms &body body) (with-argtypes '(do*) arg-forms end-forms body))
You're on your own if you want
bless your cdr.