I've recently started coding in Lisp, and have already been most impressed with macros - they allowed me to do complex loop-unrolling at compile-time, something I can't do this elegantly in any other language that I know of (i.e. code-generate while keeping the original structure).
On to optimization: I've sprinkled type annotations (lots of "the fixnum") in the same code. Once I've added 3 or 4 of them, I realized I was doing it wrong - this is what macros are for, Dont Repeat Yourself...
; whenever we want to indicate that the result of an operation
; fits in a fixnum, we macro expand (the fixnum (...))
(defmacro fast (&rest args)
`(the fixnum ,args))
...
(cond
(...)
(t (let* ((forOrange (+ (aref counts 5)
(fast * 2 (aref counts 6))
(fast * 5 (aref counts 7))
(fast * 10 (aref counts 8))))
(forYellow (+ (aref counts 3)
(fast * 2 (aref counts 2))
(fast * 5 (aref counts 1))
(fast * 10 (aref counts 0))))
...and indeed, this worked: instead of writing lots of "(the fixnum (...))" everywhere, I just quickly prefix the expression with "fast" - and all is well.
But then...
I realized that even this is not where things should stop: in principle, the macro "fast" should... be called at the TOP of the evaluation, in this case:
(forYellow (fast + (aref counts 3)
(* 2 (aref counts 2))
(* 5 (aref counts 1))
(* 10 (aref counts 0))))
...and it should recursively "plant" "(the fixnum (...))" in all subexpressions.
Can this be done? Can a "defmacro" be recursive?
UPDATE: I faced some really weird problems trying to do this, so I ended up doing what Rord suggested below - i.e. implemented a function, tested it in the repl, and calling it from the macro:
(defun operation-p (x)
(or (equal x '+) (equal x '-) (equal x '*) (equal x '/)))
(defun clone (sexpr)
(cond
((listp sexpr)
(if (null sexpr)
()
(let ((hd (car sexpr))
(tl (cdr sexpr)))
(cond
((listp hd) (append (list (clone hd)) (clone tl)))
((operation-p hd) (list 'the 'fixnum (cons hd (clone tl))))
(t (cons hd (clone tl)))))))
(t sexpr)))
(defmacro fast (&rest sexpr)
`(,@(clone sexpr)))
And it works fine under SBCL:
$ sbcl
This is SBCL 1.0.52, an implementation of ANSI Common Lisp.
...
* (load "score4.cl")
T
* (setf a '(+ (1 2) (- 1 (+ 5 6)))
...
* (clone a)
(THE FIXNUM (+ (1 2) (THE FIXNUM (- 1 (THE FIXNUM (+ 5 6))))))
* (macroexpand '(fast + 1 2 THE FIXNUM (- 1 THE FIXNUM (+ 5 6))))
(THE FIXNUM (+ 1 2 THE FIXNUM (THE FIXNUM (- 1 THE FIXNUM (THE FIXNUM (+ 5 6))))))
T
All is well, except for one side-effect: CMUCL works, but no longer compiles the code:
; Error: (during macroexpansion)
; Error in KERNEL:%COERCE-TO-FUNCTION: the function CLONE is undefined.
Oh well :-)
UPDATE: The compilation failure was addressed and solved in a different SO question.
(fast (* ...))
over(fast * ...)
. It makes the semantics simpler (consider the case(fast 3)
—what is the meaning of this form supposed to be?) and caters to the reader's expectations about the structure of forms. – Matthias Benkardmap
:(map bling xs)
. But note that this is not true for CL, where you have to write(mapcar #'bling xs)
. The reason for the difference is that Scheme is a Lisp-1, so there is no confusion about whatbling
means. In CL, when I seebling
in a non-operator position, I assume it's the variablebling
. Hence my warning of what I believe contradicts the reader's expectations. – Matthias BenkardC-1 (
. – Matthias Benkard