clith
2024-10-12
Common Lisp wITH macro. A general WITH macro.
Common Lisp wITH
Welcome to Clith!
This library defines the macro clith:with. It allows you to create some objects, bind them to some variables, evaluate some expressions using these variables, and lastly the objects are destroyed automatically.
Installation
- Manual:
cd ~/common-lisp git clone https://github.com/Hectarea1996/clith.git
- Quicklisp (Ultralisp):
(ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil) (ql:quickload "clith")
Reference
Basic usage
The macro clith:with uses WITH expanders
simarly setf
uses setf expanders
. These expanders controls how the macro clith:with is expanded.
Let's take a look at the built-in slots
expander. As the name suggest, it will expand into a with-slots
expression:
(defstruct vec2 x y) (let ((start (make-vec2 :x 5 :y 10))) (with (((x y) (slots start))) (+ x y)))
15
The macro clith:with also accepts options for each variable we want to bind. In the above example, what happens if we have two points?
(let ((start (make-vec2 :x 5 :y 10))
(end (make-vec2 :x -3 :y -4)))
(with (((x y) (slots origin))
((x y) (slots end))) ;; <-- Name collision!!
(+ x y x y)))
We should specify, as if using with-slots
, that we want to reference the slot x
or y
using another symbol.
(let ((start (make-vec2 :x 5 :y 10)) (end (make-vec2 :x -3 :y -4))) (with ((((x1 x) (y1 y)) (slots start)) (((x2 x) (y2 y)) (slots end))) (+ x1 y1 x2 y2)))
8
Defining a WITH expander
In order to extend the macro clith:with we need to define a WITH expander
. To do so, we use clith:defwith.
Suppose we have (MAKE-WINDOW TITLE)
and (DESTROY-WINDOW WINDOW)
. We want to control the expansion of WITH in order to use both functions. Let's define the WITH expander:
(defwith make-window ((window) (title) &body body) "Makes a window that will be destroyed after the end of WITH." (let ((window-var (gensym))) `(let ((,window-var (make-window ,title))) (let ((,window ,window-var)) (unwind-protect (progn ,@body) (destroy-window ,window-var))))))
make-window
This is a common implementation of a 'with-' macro. Note that we specified (window)
to specify that only
one variable is wanted.
Now we can use our expander in WITH:
(with ((my-window (make-window "My window")))
;; Doing things with the window
)
After the body of clith:with is evaluated, my-window
will be destroyed by destroy-window
.
Expander's documentation
The macro clith:defwith accepts a docstring that can be retrieved with the function documentation
. Check out again the definition of the expansion of make-window
above. Note that we wrote a docstring.
(documentation 'make-window 'with)
"Makes a window that will be destroyed after the end of WITH."
We can also setf
the docstring:
(setf (documentation 'make-window 'with) "Another docstring!") (documentation 'make-window 'with)
"Another docstring!"
Declarations
The macro clith:with accepts declarations. These declarations are moved to the correct place at expansion time. For example, consider again the example with the points, but this time, we want to ignore two arguments:
(let ((start (make-vec2 :x 5 :y 10)) (end (make-vec2 :x -3 :y -4))) (with ((((x1 x) (y1 y)) (slots start)) (((x2 x) (y2 y)) (slots end))) (declare (ignore y1 x2)) (+ x1 y2)))
1
Let's see the expanded code:
(macroexpand-1 '(with ((((x1 x) (y1 y)) (slots start)) (((x2 x) (y2 y)) (slots end))) (declare (ignore y1 x2)) (+ x1 y2)))
(with-slots ((x1 x) (y1 y)) start (declare (ignore y1)) (with-slots ((x2 x) (y2 y)) end (declare (ignore x2)) (+ x1 y2))) t
Observe that every declaration is in the right place. But how this work?
clith:with assumes that variables to be bound will be in certain places. Each variable in the declaration is searched over all the places that can contain a variable to be bound. It is searched from bottom to top. When a variable is found, a declaration of that variable is created there.
If you want to know exactly where these places are, check out the syntax of the clith:with macro:
(WITH (binding*) declaration* form*) binding ::= ([vars] form) vars ::= var | (var-with-options*) var-with-options ::= var | (var var-option*) var-option ::= form
var
are those places where a declaration can refer to.
Built-in WITH expanders
The next symbols from the package CL
has a built-in expander:
make-broadcast-stream
make-concatenated-stream
make-echo-stream
make-string-input-stream
make-string-output-stream
make-two-way-stream
open
Additionally, every macro from the package CL
whose name starts with with-
has its own expander. We've already seen an example using the expander slots
.
Since we cannot define new symbols in the package CL
, these expanders are defined in a special way. clith:with will recognize all the symbols (for any package) whose name is equal to the name of the expander.
The complete list is:
CL Standard macro | WITH expander |
with-accesors | accesors |
with-compilation-unit | compilation-unit |
with-condition-restarts | condition-restarts |
with-hash-table-iterator | hash-table-iterator |
with-input-from-string | input-from-string |
with-open-file | open-file |
with-open-stream | open-stream |
with-output-to-string | output-to-string |
with-package-iterator | package-iterator |
with-simple-restart | simple-restart |
with-slots | slots |
with-standard-io-syntax | standard-io-syntax |