This library implements the let+
macro, which is a dectructuring
extension of let*
.
- clean, consistent syntax and small implementation (less than 300 LOC, not counting tests)
- placeholder macros allow editor hints and syntax highlighting
&ign
for ignored values (in forms where that makes sense)- very easy to extend
This library was inspired by Gary King’s excellent metabang-bind. I have been using the latter for years now, but at some point I decided to write a library of my own, aiming for a cleaner syntax, more concise implementation and a more consistent interface (whether I have succeeded is of course a matter of judgement — try metabang-bind to see if you like it better).
In my opinion the main advantages of this library, compared to
metabang-bind, are the placeholder macros which provide editor hints
and the more consistent syntax of destructuring forms. In particular,
when both read-write and read-only forms are available the latter
always have the -r/o
suffix, &flet
and &labels
resemble the
Common Lisp syntax more closely, and the library should be easier to
extend.
You can find other pattern matching libraries on cliki.
let+ ({binding}*) body*
where
binding ::= symbol || (form [init-form])
LET+
is recursive: each binding is in the scope of the previous
ones. Forms ignore &ign
variables (where applicable).
Forms which provide both read-write and read-only access are available as &form
and &form-r/o
. The first one always uses symbol macros, so you can use setf
. The second one reads the values at the beginning of the list from value: you can change these variables after that without having any effect on the original value. Read-only forms may also provide a slight increase in speed, and promote good style — you can use them to signal that you will not change the original structure.
The following forms are defined:
var
,(var)
,(var value)
- These behave just like they do in
let*
. (list value)
- When
list
is not recognized as any of the forms below, it is simply destructured usingdestructuring-bind
.&ign
are ignored. Example:
(let+ (((a (b &optional (c 3)) &ign &key (d 1 d?)) '(1 (2) 7 :d 4)))
(list a b c d d?)) ; => (1 2 3 4 T)
((&slots slot*) value)
, also&slots-r/o
- Similarly to
with-slots
, eachslot
has the syntaxvariable
or(variable)
(for these, the variable name is also used for the slot name) or(variable slot-name)
.&slots-r/o
provides read-only bindings.Example:
(defclass foo-class ()
((a :accessor a :initarg :a)
(b :accessor b-accessor :initarg :b)))
(let+ (((&slots a (my-b b)) (make-instance 'foo-class :a 1 :b 2)))
(list a my-b)) ; => (1 2)
((&accessors accessor*) value)
, also&accessors-r/o
- Syntax similar to
&slots
, but uses accessors. Continuing the example above:
(let+ (((&accessors a (b b-accessor)) (make-instance 'foo-class :a 1 :b 2)))
(list a b)) ; => (1 2)
((&structure conc-name slot*) value)
, also&structure-r/o
- Slot access for structures.
Conc-name
is prepended to the accessors (you need to include the-
if there is one). Example:
(defstruct foo-struct c d)
(let+ (((&structure foo-struct- c (my-d d)) (make-foo-struct :c 3 :d 4)))
(list c my-d)) ; => (3 4)
((&values value*) form)
- Similar to
multiple-value-bind
.&ign
are ignored. Example:
(let+ (((&values a &ign b) (values 1 2 3)))
(list a b)) ; => (1 3)
(array value)
(only read-only version)- The array is
destructured to the given elements,
&ign
are ignored. Indexes use row-major access, determined at macroexpansion time. Example:
(let+ ((#(a &ign b) (vector 1 2 3)))
(list a b)) ; => (1 3)
((&array-elements (variable subscript*)*) value)
, also&array-elements-r/o
- Array elements with given subscripts are assigned to the variables. Example:
(let+ (((&array-elements (a 0 1)
(b 2 0))
#2A((0 1)
(2 3)
(4 5))))
(list a b)) ; => (1 4)
((&flet name lambda-list forms*))
, also&labels
- Function bindings. These have no value form.
&labels
allows the function to refer to itself – note that sincelet+
is always recursive, this is the only difference between the two forms. Example:
(let+ (((&flet add2 (x)
(+ x 2))))
(add2 5)) ; => 7
((&plist (variable key [default])*)
, also&plist-r/o
- Access to property lists. When
key
is not given,variable
is used instead, anddefault
is used if the element does not exist in the value (note that default may be evaluated multiple times when using the read-write form which usessymbol-macrolet
). Example:
(let+ (((&plist a (my-b b) (c nil 3)) '(a 1 b 2)))
(list a my-b c)) ; => (1 2 3)
(((&hash-table (variable key [default])*)
, also&hash-table-r/o
- Access to the elements of hash tables, the semantics is the same as
&plist
. (&complex real imaginary)
- Destructures complex numbers.
You can nest let+
expressions when it makes sense (it doesn’t always, especially for read/write slots, the read only form should work). For example,
(let+ ((#((&complex a b)) (vector (complex 1 2))))
(list a b))
should destructure the complex number that is the single element in the vector.
If you find that let+
does not nest properly, please report it as a bug.
(defun+ name (argument*) form*)
, also(lambda (argument*) form*)
- Work like
defun
andlambda
, but arguments are destructured usinglet+
. Example:
(defun+ foo ((&plist a b c) #(d e))
(list a b c d e))
(foo '(a 1 b 2 c 3) #(4 5)) ; => (1 2 3 4 5)
See also &labels+
and &lambda+
.
define-structure-let+
- Can be used to provide destructuring forms for structures.
(&once-only symbols ...)
and(&with-gensyms symbols)
are useful for writing macros.
Extending let-plus
is very easy: if you want to use a form that
resembles a list, you just have to define a method for
let+-expansion-for-list
. There is a macro that helps you with that,
called define-let+-expansion
. If the library didn’t have
&complex
, we could define destructuring for the form like this:
(define-let+-expansion (&complex (x y))
"Access real and imaginary part of the value. Read-only."
`(let ((,x (realpart ,value))
(,y (imagpart ,value)))
,@body))
Some highlights:
- this macro defines a “placeholder” macro
&complex
that should help with editor hints, but has no other purpose (it is not used in the expansion), - the macro is anaphoric, capturing
value
(the value form) andbody
(the body inside thelet+
form), you can customize both of this using keyword arguments, - unless required otherwise,
value
is wrapped inonce-only
preventing multiple evaluations of the same form. See the arguments:uses-value?
and:once-only?
fordefine-let+-expansion
.
If you want to extend let+
with forms that are not lists (eg like
the array syntax above), have a look at let+-expansion
.
Please open an issue on Github for bugs. Extensions are also welcome, either as forks or small code snippets submitted as issues. Wishlist items are also welcome!
I ask you not to report bugs via e-mail if you can avoid it. Tracking bugs on Github makes it less likely that they get lost.