Misc-Extensions is a small library of macros that I used in FSet and which FSet users might also find handy to use in their code. One of these is an extensible iteration macro called GMap. The rest are discussed here.
Of these, mvlet and mvlet* are especially useful for dealing with FSet’s
API, which makes heavy use of multiple values. fn is handy for writing higher-order code in
general (though experienced CLers are probably using some similar macro already). The remainder,
well, have a look — maybe you’ll see something you like.
Exported from package misc-extensions.new-let.
For writing code in a mostly-functional style, Common Lisp’s multiple-value feature is extremely
useful (I make heavy use of it both internally within FSet and in the API). But the most important
primitive the language provides for receiving multiple values from a call,
multiple-value-bind, is a bit clunky to use. First, its long name would be appropriate for a
rarely used operation, but in the mostly-functional style, it can be used quite frequently.
Second, combining it with other bindings can be done only by nesting; where cl:let and
cl:let* let you bind several variables to the values of their respective init-forms,
multiple-value-bind receives values from only a single form.
Misc-Extensions provides three related macros to elegantly and succinctly bind lists of one or more variables to the one or more values of their respective init-forms. All of these allow more than one variable in a binding clause; the last element of the clause is the init-form, and the preceding elements are the variables to be bound to the values of the init-form.
The two that I expect most people will want to use are mvlet and mvlet*.
mvlet, like cl:let, makes all bindings in parallel, so the init-forms can refer to
variables of the same names in the containing scope. mvlet*, like cl:let*, makes them
sequentially, so each init-form can refer to any of the variables bound in the preceding clauses.
Except for being able to bind multiple values, they are upward-compatible with the CL operators.
nlet generalizes the other two by making it possible to do any combination of parallel and
sequential binding. It allows the binding clauses to be nested to indicate sequential binding: more
deeply nested clauses are within the scopes of bindings made by less deeply nested clauses. Within
a level, bindings are parallel. I’m personally fond of nlet, but some people find the
heavily parenthesized syntax a little off-putting.
Here are examples of all three.
(let ((a (foo)))
...
(mvlet ((a b (bar))
(c (baz a)) ; outer 'a'
...)
...)
(mvlet* ((a b (bar))
(c (baz a)) ; inner 'a'
...)
...)
(nlet ((a b (bar))
(c (baz a)) ; outer 'a'
...)
...)
(nlet ((a b (bar))
((c (baz a))) ; inner 'a'
...)
...))
In all four examples we see a and b being bound to the first two values of
(bar) in the first clause, but whether the reference to a in the second clause refers
to the a in the outer scope or the one that was just bound depends on whether mvlet or
mvlet* was used, or in the nlet case, the nesting level of the second clause.
A more complex nlet example:
(nlet ((a b c (zot))
((d (quux a c))
((e f (mumble b d))
(g (mung a))))
((h (frobozz c))
((i (xyzzy h))))
(*print-level* 3))
...)
First a, b, and c are bound to the first three values of (zot), and in
parallel, *print-level* is bound to 3; then d and h are bound; then e,
f, g, and i are bound.
As this example illustrates, all bindings at a given nesting level are done in parallel, with all
bindings at a deeper level following. Stylistically, it is expected that init-forms in nested
clauses will refer only to variables bound in containing clauses. However, this is not enforced; for
instance, the init-form for g could refer to h, since the latter is bound one level
out.
The macros correctly handle declare forms at the beginning of the body, emitting the
declarations at the correct level within the expansion so that they apply to the binding named.
The symbol nlet is exported from package new-let. It also exports the same macro under
the name let, so that it can shadow cl:let; this is how I use it, though again, I
suspect most people will not want to do that. Anyway, because new-let:let is exported, if you
(:use :new-let) in your package declaration, you will also need to include a
:shadowing-import-from clause to say which version of let you want to use.
mvlet and mvlet* are initially exported from new-let as well, but to make them
a little more convenient to use (especially for those not yet expert in dealing with CL packages), I
created another package mvlet that re-exports only these two symbols. So you can say
(:use :mvlet) to get them, without having to shadowing-import anything.
Historical note: I first wrote nlet in 1980, on the MIT Lisp Machine, and have used it ever
since in my own projects.
(Paul Rodriguez’s Serapeum also includes
implementations of mvlet and mvlet*.)
Exported from package misc-extensions.new-let.
For very small lambda expressions, the six characters taken by the word lambda can be a
significant fraction of the total. Additionally, if the body doesn’t reference all the parameters,
you’ll want to add a (declare (ignore ...)) for the unused ones, which adds to the verbosity.
The fn macro helps with both annoyances. Obviously, its name is quite short. (Those willing
to set foot on the slippery slope of Unicode could use λ, of course, but I’ve been afraid to
go there, lest my code wind up as an unintelligible mass of obscure mathematical symbols and cat
emojis 🙀.) Also, you can use either a bare _ or a name beginning with _ as a parameter
name, to indicate an ignored parameter; fn automatically inserts the required ignore
declaration.
One catch, though, is that if you inadvertently write #'(fn ...), you will get a
probably-unintelligible compiler error. Just delete the #'.
Declares var as a global lexical (non-special) variable, and if val is supplied and
var is not already bound, initializes it to val. doc, if supplied, is taken as
a documentation string. If the same name is later locally bound (e.g. witih let), that
will create a new lexical variable, with no effect on the global one.
Declares var as a global lexical (non-special) variable and sets it to val; so if
var was already initialized, this reinitializes it, unlike plain deflex.. doc,
if supplied, is taken as a documentation string. If the same name is later locally bound (e.g.
witih let), that will create a new lexical variable, with no effect on the global one.
Some implementations, notably CMUCL and SBCL, issue a rather verbose warning if you use setq
to set a previously undeclared global variable in the REPL. I find this annoying. I could use
defvar or defparameter instead, but they return the name of the variable, not its
value, so if I want the value printed, I have to enter the name at the next prompt. Besides, using
one of those declares the variable special, so I have to be careful to put *earmuffs* on my
variable names. The other Lisps I’ve used for many years have quietly accepted setq of a new
variable in the REPL, without declaring it special.
So I wrote a macro isetq (“interactive setq”). It uses the same technique as
deflex to make the variable being assigned to into a global lexical, so I don’t need to use
earmuffs to prevent name collisions with local variables in my code.
isetq is only for interactive use; it is not recommended for use in programs.
It’s not uncommon to use labels to define a set of several mutually-recursive functions,
whose code can fill a screen or two, then have the body of the labels kick off the recursion
with a one-liner that just calls one of the functions. This strikes me as a little hard to read —
you have to scroll all the way to the bottom to find the initial values of the recursion variables
— and also a little wasteful of indentation space. So I introduced a macro rlabels
(“reversed labels”), which puts the initial call first as a single subform, then takes the
function-binding clauses as its &body parameter, saving seven columns of indentation.
The effect on code layout is simiiar to that of the named-let macro that some people use, but
it’s more general as it can define multiple local functions.
There are also rflet and rmacrolet, but they’re rarely useful.
As everyone knows, defclass forms tend to be rather verbose and repetitive:
(defclass frob (widget)
((color :initarg :color :reader frob-color
:initform (error "A color must be specified.")
:documentation "The color of the frob.")
(height :initarg :height :accessor frob-height
:initform 3.0
:documentation "The height of the frob in cm."))
(:documentation "The class of all frobs."))
I’ve written a define-class macro to shorten them. It is completely upward compatible with
defclass; you could just replace defclass with define-class and have a valid
invocation that would produce the same result. But define-class provides several features to
make the definition more succinct. Use only the ones you like! The big change is that where
defclass slot descriptions have a strict alternating keyword-value syntax,
define-class is more flexible.
(slot-name initform); an :initform option will be generated.
:may-init generates :initarg :slot-name.
:must-init generates :initarg :slot-name, and also an :initform that signals an
error if the argument is not provided to make-instance.
:readable generates :reader gf-name, and :accessible generates
:accessor gf-name, where gf-name is either the slot name or, if a :conc-name
class option is supplied, that string prepended to the slot name.
:constant is an abbreviation for :must-init :readable if the spec
contains no initform, or :may-init :readable if there is an initform (in either syntax).
:variable is an abbreviation for :may-init :accessible.
:documentation will be inserted.
:doc as an abbreviation for :documentation.
So, here’s the above example using define-class; 347 characters have been reduced to 186, and
I think it’s actually easier to understand:
(define-class frob (widget) "The class of all frobs." ((color :constant "The color of the frob.") ((height 3.0) :variable "The height of the frob in cm.")) (:conc-name #:frob-))
Let me emphasize: you can still use the defclass slot options in any case where the above
features do not do what you want; for instance, if you want a different reader/accessor name for a
particular slot than what define-class would have used.