Hello,
It has been a long time since I posted something here. I have been busy with my day job and bogged down in a major rewrite of something (more on this hopefully very soon now (tm)) that is full of rabbit's holes.
I was able to get out of one of these rabbit's holes with this little hack I cooked up that allows you to, possibly, write more concise code.
This little hack introduces two handy Common Lisp
macros, LETV
and LETV*
that allow you to mix
regular LET
and MULTIPLE-VALUE-BIND
forms in
a less verbose way. The amount of indentation needed is also reduced.
The syntax of LETV
(or LETV*
) is very
"loopy", with a nod to SML/OCaml/F#/Haskell/Julia. The current syntax
is the following:
letv ::= 'LETV' [vars] [IN [body]] letvstar ::= 'LETV*' [vars] [IN [body]] vars ::= var | var vars var ::= ids '=' <form> [decls] decls ::= decl | decl decls decl ::= OF-TYPE idtypes ids ::= <symbol> | '(' <symbol> + ')' idtypes ::= <type designator> | '(' <type designator> + ')' body ::= [<declarations>] <form> *
(I know: the grammar is not completely kosher, but I trust you will understand it.)
The two macros expand in forms that mimic the semantics of
let
, LET*
and
MULTIPLE-VALUE-BIND
. All type declarations, if present,
are properly handled via locally
forms.
LETV
expands into a LET
, with variable
initialized by PSETQ
s and
multiple-VALUE-SETQ
s.
LETV*
expands into a form that is an interleaving of
LET*
and MULTIPLE-VALUE-BIND
.
The library exports only the two symbols LETV
and
LETV*
. The other "symbols" mentioned (=
,
IN
, OF-TYPE
) are checked as in
LOOP
; therefore you can use different styles to write
your code, as you would when writing LOOP
s.
The library is available here.
Examples
- Simple case:
(letv x = 42 in (format t "The answer is ~D.~%" x))
- Same with declaraion:
(letv x = 42 of-type fixnum in (format t "The answer is ~D." x))
- Simple case with
MULTIPLE-VALUE-BIND
:(letv (v found) = (gethash 'the-key *my-hash-table*) in (if found (format t "Found THE-KEY, doing stuff.~%") (error "THE-KEY not found.")))
- Mixing things up:
(letv (v found) = (gethash 'the-key *my-hash-table*) of-type (fixnum boolean) x = 42 in (if found (format t "Found THE-KEY, adding to the answer ~D.~%" (+ v x)) (error "THE-KEY not found.")))
- With
LETV*
:(letv* (v found) = (gethash 'the-key *my-hash-table*) of-type (fixnum boolean) x = (when found (+ v 42)) in (if found (format t "Found THE-KEY, adding to the answer ~D.~%" x) (error "THE-KEY not found.")))
- The other way around.
(letv x = 42 of-type integer (v found) = (gethash 'the-key *my-hash-table*) of-type (fixnum boolean) in (if found (format t "Found THE-KEY, adding to the answer ~D.~%" (+ v x)) (error "THE-KEY not found.")))
- A more compelling example.
(letv* (p found) = (gethash 'the-key *points-table*) of-type (point) (x y) = (if found (unpack-point p) (values :missing :missing)) in (declare (type point p) (type real x y)) ; Adding declarations here also works. (do-stuff-with x y) (do-things-with p))
All the examples are meant to illustrate the use of
LETV
and LETV*
.
Notes
LETV
and LETV*
are obviously not the first
macros of this kind floating around: others are available and all have
their niceties. But I never claimed not to suffer from NIH syndrome.
LETV
and LETV*
do not do "destructuring" or
"pattern matching". That is a different can of worms; but you can
check cl-unification for a
library (always by yours truly) that provides facilities in that
sense.
'(Cheers)