Racket's documentation has an excellent tutorial on using macros.
I would definitely recommend Scheme macros over CL macros if you haven't deal with any sort of Lisp macros before. That's because Scheme macros use pattern matching, and it's much easier to read.
Example (using Racket's define-syntax-rule
):
(define-syntax-rule (let ((var val) ...)
expr ...)
((lambda (var ...)
expr ...)
val ...))
This is a very simple macro that defines let
in terms of creating a corresponding lambda, then applying it. It's easy to read, and easy to reason about what it does.
Slightly more complicated macro:
(define-syntax let*
(syntax-rules ()
((let* ()
expr ...)
(let ()
expr ...))
((let* ((var val) next ...)
expr ...)
(let ((var val))
(let* (next ...)
expr ...)))))
This defines let*
in terms of nested let
s, so that the bindings are done sequentially. It includes a base case (with no bindings), as well as a recursive case.