Monday, May 23, 2011

Introducing LET+, a destructuring extension of LET*

Why another destructuring library?

This Common Lisp library extends the syntax of let*. In that respect it is pretty similar to other destructuring libraries, most importantly 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).

This library differs from metabang-bind in the following ways:

  1. Placeholder macros like &slots, mainly for providing editor hints (completion, eg syntax reminders in the Emacs status bar with SLIME, displaying of arguments, etc).
  2. More consistent naming of destructuring forms. In particular, when both read-write and read-only forms are available the latter always have the -r/o suffix
  3. &flet and &labels resemble the Common Lisp syntax more closely.
  4. The code is extremely simple (< 300 loc, not counting unit tests), and the library should be easier to extend.


Lists are destructured unless recognized as something else (values mapping to NIL are ignored):

(let+ (((a (b &optional (c 3)) nil &key (d 1 d?)) '(1 (2) 7 :d 4)))
  (list a b c d d?))  ; => (1 2 3 4 T)

Slots and accessors have a syntax similar to with-slots:

(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)

(let+ (((&accessors a (b b-accessor)) (make-instance 'foo-class :a 1 :b 2)))
  (list a b))  ; => (1 2)

Slots in structures can be accessed by giving the conc-name:

(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)

but if you use defstruct+ to define your structure, you automatically get a corresponding deconstructing form:

(defstruct+ interval left right)

(let+ ((interval (make-interval :left 1 :right 2))
       ((&interval left right) interval))
  (incf right 10)
  interval)  ; => #S(INTERVAL :LEFT 1 :RIGHT 12)

Multiple values are also supported:

(let+ (((&values a nil b) (values 1 2 3)))
  (list a b))  ; => (1 3)

You can access array elements in many different ways:

(let+ ((#(a nil b) (vector 1 2 3)))
  (list a b))  ; => (1 3)

(let+ (((&array-elements (a 0 1)
                         (b 2 0))
        #2A((0 1)
            (2 3)
            (4 5))))
  (list a b))  ; => (1 4)

You can use &flet (and &labels, if the function needs to refer to itself) to write functions:

(let+ (((&flet add2 (x)
          (+ x 2))))
  (add2 5))  ; => 7

&plist and &hash-tables allow access to elements property lists and hash tables.

Read-only forms

All of the &... forms have a read-only version, eg &slots-r/o. With these, the values are read at the beginning, and you can modify them without modifying the original structure. Read-only forms may also give you a slight increase in speed, and promote better style by indicating that you are not modifying the original structure.

How to get it

The library is a available on Github, under the Boost Software License (Version 1.0). The README file there serves as the documentation, see the source code and unit tests for mode details. As always, bug reports and suggestions are welcome.