In this post I'm going to concentrate on Shen macros; some familiarity
with basic Shen and Common Lisp macros is assumed. In Shen, as in
traditional Lisp style, code is data and data is code. Shen code is read
in as a list datastructure:
(+ 1 2) becomes
[+ 1 2]. Shen macros are
functions that pattern match on the list representation of code at read
time in order to rewrite it. This pattern matching is very similar to
some Scheme macro systems (which I've not used before) though in Shen
our macro language is the same as our language for function definition.
The syntax for a Shen macro is as follows:
\* defmacro syntax *\ (defmacro macro-name \* 1st pattern match *\ -> \* rewrite rule 1 *\ \* 2nd pattern match *\ -> \* rewrite rule 2 *\ \* ... *\)
The first argument to defmacro is the macro's name. This is not the same
as a Common Lisp defmacro where the name is used to indentify where the
macro should expand, i.e.
(macro-of-great-justice ...) but is the name
of the function that operates at read time on the list representation of
the code. Then, using identical syntax to define, a number of pattern
matches with associated rewrite rules can be supplied.
In this first example I'll show something reasonably useful: a pipe
operator. This operator is used in F# (and trivial to implement in
OCaml) and Clojure with slightly different syntax. In normal
S-expression function application the inner S-exp is the first to be
evaluated with evaluation moving outwards. The textual representation is
'backwards' compared to the order of the computation. A pipe operator
allows us to write out a computation in the order we would consider it
occuring. For example, if we want to apply (in order) functions
x we would typically write:
(D (C (B (A x)))). In F# with the
pipe operator we could rewrite this as
x |> A |> B |> C |> D and in
(-> x A B C D). For the Shen pipe operator I will use the
(>> x A B C D). The code for this macro is shown below.
(defmacro pipe [>> X F] -> [F X] [>> X F | Fs] -> [>> [F X] | Fs])
The macro is given the name pipe but again this name will not be used when we want to use the operator. The macro operates thus:
The first line states that if we see an S-expression starting with
and two arguments (here we use the place holder symbols
F) we want
to rewrite that S-expression to give the application of
The second line states if we see an S-expression starting with
three or more arguments (
Fs) we want to rewrite that S-expression
to apply the pipe operator to the value
[F X] and some other functions
If more than one function is supplied the macro will recur on the second
rule until a single function remains at which point the first rule
triggers and the macro expansion is complete.
Using this in the repl:
(1-) (define sq X -> (* X X)) sq (2-) (>> 2 sq sq sq) 256
As I mentioned above the first argument to defmacro names the read time
function operating on the list representation of the Shen code. This
really is a function and can be called as such: calling
(pipe [>> 2 sq
sq]) will yield the first iteration of the pipe macro. Shen provides the
function macroexpand which will output to completion the macroexpansion
of a form using all the currently loaded macros (in Common Lisp a
similar thing is achieved with macroexpand-1).
(3-) (pipe [>> 2 sq sq]) [>> [sq 2] sq] (4-) (pipe (pipe [>> 2 sq sq])) [sq [sq 2]]
I'm tempted to claim (and please correct me if I'm wrong) that Shen macros are slightly more general than Common Lisp defmacros because they can be triggered on an arbitrary pattern match of code instead of being triggered by a macro name in the 1st position of a sexp as in Common Lisp. Here's an example, we can rewrite the pipe macro to work infix as in F# with the following code1.
(defmacro pipe-infix [X >> F] -> [F X] [X >> F >> | Fs] -> [[F X] >> | Fs])
In the repl:
(2-) (2 >> sq >> sq >> sq) 256
I hope you've found these basic examples helpful. In future I would like to talk more about the differences between Shen macros and Common Lisp macros (e.g. why Shen doesn't have quasiquote/unquote/unquote-splicing).
Post title from The Algebraist by Iain M. Banks