expanders
2025-06-22
Tool for defining expanders.
Expanders
Welcome to Expanders!! :D
What is this?
The project lets you define expanders
. An expander
is like a namespace for forms that should transform in a specific way.
It easier to understand with an example. Suppose we have the following form (op (+ 3 4) b)
. This form will be used in two different macros named plus-macro
and minus-macro
. Each macro receives a form. That form could start with op
. In that case, op
is substituted by +
or -
respectively.
Let's define 2 different expanders:
;; We assume (use-package #:expanders) (defexpander plus-expander) (defexpander minus-expander)
minus-expander
Now we have two different expanders. The function exp:expanderp can tell us if a symbol denotes an expander:
(expanderp 'plus-expander)
t
(expanderp 'hey)
nil
(expanderp 'minus-expander)
t
Now it is time to define the expansion op
for each expander using exp:defexpansion:
(defexpansion plus-expander op (a b) "OP to + expansion" `(+ ,a ,b)) (defexpansion minus-expander op (a b) "OP to - expansion" `(- ,a ,b))
op
We can check if a symbol is an expansion for a given expander using exp:expansionp:
(expansionp 'plus-expander 'op)
t
(expansionp 'plus-expander 'hey)
nil
(expansionp 'minus-expander 'op)
t
Also, we can retrieve or set the docstring using documentation
:
(documentation 'op 'plus-expander)
"OP to + expansion"
(let ((old-docstring (documentation 'op 'minus-expander))) (setf (documentation 'op 'minus-expander) "Another docstring") (let ((new-docstring (documentation 'op 'minus-expander))) (format t "Old: ~s~%New: ~s" old-docstring new-docstring)))
Old: "OP to - expansion" New: "Another docstring"
nil
We can expand a form using exp:expand. Note that the form must be a list starting with the name of an expansion:
(expand 'plus-expander '(op 3 (+ 5 6)))
(+ 3 (+ 5 6))
(expand 'minus-expander '(op 3 (+ 5 6)))
(- 3 (+ 5 6))
Finally, let's define the macros plus-macro
and minus-macro
:
(defmacro plus-macro (form) (if (and (consp form) (expansionp 'plus-expander (car form))) (expand 'plus-expander form) form))
plus-macro
(defmacro minus-macro (form) (if (and (consp form) (expansionp 'minus-expander (car form))) (expand 'minus-expander form) form))
minus-macro
If we use the form (op 5 4)
we will see that each macro will expand to (+ 5 4)
or (- 5 4)
respectively.
(plus-macro (op 5 4))
9
(minus-macro (op 5 4))
1
Why?
- It is common: I have noticed that having expanders is a relatively common pattern in macros. The best example is
setf
and itssetf-expanders
. Another project using expanders is CFFI and its type parsers. In my own projects I ended up using the same techniques (Clith). - Duality of syntax: We can increase the duality of syntax using expanders. The best example is
setf
. Thanks tosetf
we don't need names for setters because they come for free with the getter.
Reference
Macro: exp:defexpander (sym)
Defines an expander represented by the symbol SYM.
Macro: exp:defexpansion (expander name (&rest args) &body body)
Defines an expansion for the expander EXPANDER. NAME must be a symbol denoting the new expansion. ARGS is a destructuring lambda list. This must return the desired expansion for NAME and EXPANDER.
Function: exp:expand (expander form)
Expands a form using an expander. FORM must be a list starting with a valid expansion symbol for the expander EXPANDER.
Function: exp:expanderp (sym)
Check if a symbol denotes an expander.
Function: exp:expansionp (expander expansion)
Checks if EXPANSION is a valid expansion for the expander EXPANDER. EXPANDER must be a valid expander.