;* Preliminaries
Most Common Lisp control constructs (for example: defun
, cond
,
let
, labels
) allow more than one expression in the body. These are
then executed in sequence, and the result of the final one is
returned.
Example
(defun hello() (print "hello") (print "how") (print "are you?")) CL-USER> (hello) "hello" "how" "are you?" "are you?" CL-USER>
A convenient contruct for iteration
CL-USER> (dolist (i '(a b c)) (print i)) A B C NIL CL-USER>
Another convenient contruct for iteration
CL-USER> (dotimes (i 3) (print i)) 0 1 2 NIL CL-USER>
(defun sum (&rest l) (reduce #'+ l :initial-value 0)) CL-USER> (sum 1 2 3 4 5) 15
(defun seq (&key start end step) (list start end step)) (seq :start 10 :end 1 :step -1)
If you have trouble remembering the order of the arguments in your function, or want a function with an arbitrary number of arguments
Gensym creates an uninterned symbol. Looks strange when printed, since it is not interned and belongs to no package.
CL-USER> (gensym) #:G844 CL-USER> (gensym "X") #:X845
Macros build Lisp programs. Since the resulting expressions may be complex, Common Lisp has a special syntax for building complex nested list structures.
`(,x b)might expand to
(list x 'b)and evaluate to
(42 b)if
x
is 42.
Generally speaking, anything preceeded by a comma (,
)is evaluated as
a Lisp expression and the value inserted.
Anything else is inserted as it is.
`(a (+ 1 2) c) `(a ,(+ 1 2) c) `(a (list 1 2) c) `(a ,(list 1 2) c) `(a ,@(list 1 2) c)
Expressions preceeded by ,@
must evaluate to a list. The elements of
the list are inserted.
Basic idea: The macro is evaluated (without first evaluating the arguments). Then the result of the macro evaluation is evaluated as a Lisp expression.
When does the first evaluation (macroexpansion) occur?
This means that we do not have access to run-time information when expanding the macros. No point in trying to evaluate expressions etc.
SBCL compiles all functions in one file at one go. Suppose a file contains
Now, the macro cannot use the function before it has been compiled. Thus, the use of the macro cannot be compiled...
Now, let's look at some examples
Common Lisp already defines an if
, but let's define one anyway:
(defmacro my-if (condition then else) `(cond (,condition ,then) (t ,else))) (defun fak (n) (my-if (= n 0) 1 (* n (fak (- n 1)))))
CL-USER> (fak 10) 3628800 CL-USER> (macroexpand-1 '(my-if (= n 0) 1 (* n (fak (- n 1))))) (COND ((= N 0) 1) (T (* N (FAK (- N 1))))) T
Next, try macroexpand
, which repeats macroexpansion.
(defmacro seq (&body body) `(let () ,@body)) CL-USER> (seq (print "foo") (print "bar") 32) "foo" "bar" 32
(do-interval (x 0 10) (print x)) CL-USER> (do-interval (x 0 3) (print x)) 0 1 2
I need to introduce temporaries (use gensym
)
General looping: Use block
+ loop
+ return-from
(block foo (loop ... (when ... (return-from foo value)) ...))
We could also use labels
.
(defmacro do-interval (var-a-b &body body) (let ((blck (gensym "BLOCK")) (a1 (gensym "A")) (b1 (gensym "B")) (var (car var-a-b))) `(let* ((,a1 ,(cadr var-a-b)) (,b1 ,(caddr var-a-b)) (,var ,a1)) (block ,blck (loop (unless (< ,var ,b1) (return-from ,blck nil)) ,@body (incf ,var))))))
destructuring-bind
simulates Common Lisp's argument passing.
Example:
(destructuring-bind (a b c) '(1 2 3) (format t "[~w ~w ~w]" a b c))Nice, but too long!
Introduce a shorthand!
(defmacro dbind (alist value &rest body) `(destructuring-bind ,alist ,value ,@body))
First, lisps ecase
.
Like switch of other languages..
(defun foo (x) (ecase x (42 "fortytwo") (foo "bar"))) CL-USER> (foo 42) "fortytwo" CL-USER> (foo 'foo) "bar" CL-USER> (foo 'something-else) ; Evaluation aborted.
I like the pattern matching of Erlang and SML.
Wouldn't it be cool to be able to write something like this?
(defun foo (x) (dcase x ((leaf data) (format t "A leaf with data ~a~%" data)) ((node left right) (format t "A node with left ~a and right ~a~%" left right))))
(defmacro dcase (index &rest clause-list) (let ((var (gensym))) (labels ((translate-clause (clause) (let ((arg-list (cdar clause))) `((,(caar clause)) (destructuring-bind ,arg-list (cdr ,var) ,@(cdr clause)))))) `(let ((,var ,index)) (ecase (car ,var) ,@(map 'list #'translate-clause clause-list))))))
Why dolist
but no do-vector
?
(defmacro do-vector (var-seq &body body) (destructuring-bind (var seq) var-seq (let ((vector (gensym "VECTOR")) (index (gensym "INDEX"))) `(let ((,vector ,seq)) (dotimes (,index (length ,vector)) (let ((,var (aref ,vector ,index))) ,@body))))))
Try
(do-vector (x "foo bar") (print x))
It's is sometimes useful to put literal data into a table.
(defmacro defmap (name options &rest entries) (declare (ignore options)) (let ((table (make-hash-table))) (dolist (entry entries) (setf (gethash (car entry) table) (cadr entry))) `(defvar ,name ',table))) ; Example (defmap *a* () (azure 42) (bar 99) (clojure 123) (data 987) (email 1009))
But iterating over a hash table is tiresome...
(defmacro do-hash-table (key-value-ht &rest body) (destructuring-bind (key value ht) key-value-ht (let ((iterator (gensym "ITERATOR")) (next (gensym "NEXT"))) `(with-hash-table-iterator (,iterator ,ht) (loop (multiple-value-bind (,next ,key ,value) (,iterator) (unless ,next (return nil)) ,@body))))))
CL-USER> (do-hash-table (key var *a*) (format t "[~w ~w ]~%" key var)) [EMAIL 1009 ] [DATA 987 ] [CLOJURE 123 ] [BAR 99 ] [AZURE 42 ]
(collect.lisp
)
Problems with macros
(def... <identifier> ... (with-... (...) <body>) (do-... <body> )
Simple macros — complex ones
Application-specific macros — general purpose ones.
Macros can also be used to introduce a completely new notation...
Example: cl-yacc