#+TITLE: =let+=: destructuring extension of =let*=
#+AUTHOR: Tamás K. Papp

This library implements the =let+= macro, which is a dectructuring
extension of =let*=.

* Highlights

- 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

* Similar libraries

This library was inspired by Gary King's excellent [[http://common-lisp.net/project/metabang-bind/][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 [[http://common-lisp.net/project/metabang-bind/][metabang-bind]] to
see if you like it better).

In my opinion the main advantages of this library, compared to
[[http://common-lisp.net/project/metabang-bind/][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 [[http://www.cliki.net/pattern%20matching][cliki]].

* Syntax

#+BEGIN_SRC lisp
let+ ({binding}*) body*
#+END_SRC
where
#+BEGIN_SRC lisp
binding ::= symbol || (form [init-form])
#+END_SRC

=LET+= is recursive: each binding is in the scope of the previous
ones.  Forms ignore =&ign= variables (where applicable).

** Built-in forms

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 using =destructuring-bind=.  =&ign= are ignored.  Example:
#+BEGIN_SRC lisp
(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)
#+END_SRC

- =((&slots slot*) value)=, also =&slots-r/o= :: Similarly to =with-slots=, each =slot= has the syntax =variable= 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:
#+BEGIN_SRC lisp
(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)
#+END_SRC

- =((&accessors accessor*) value)=, also =&accessors-r/o= :: Syntax similar to =&slots=, but uses accessors.  Continuing the example above:
#+BEGIN_SRC lisp
(let+ (((&accessors a (b b-accessor)) (make-instance 'foo-class :a 1 :b 2)))
  (list a b))  ; => (1 2)
#+END_SRC

- =((&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:
#+BEGIN_SRC lisp
(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)
#+END_SRC

- =((&values value*) form)= :: Similar to =multiple-value-bind=.  =&ign= are ignored.  Example:
#+BEGIN_SRC lisp
(let+ (((&values a &ign b) (values 1 2 3)))
  (list a b))  ; => (1 3)
#+END_SRC

- =(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:
#+BEGIN_SRC lisp
(let+ ((#(a &ign b) (vector 1 2 3)))
  (list a b))  ; => (1 3)
#+END_SRC

- =((&array-elements (variable subscript*)*) value)=, also =&array-elements-r/o= :: Array elements with given subscripts are assigned to the variables.  Example:
#+BEGIN_SRC lisp
(let+ (((&array-elements (a 0 1)
                         (b 2 0))
        #2A((0 1)
            (2 3)
            (4 5))))
  (list a b))  ; => (1 4)
#+END_SRC

- =((&flet name lambda-list forms*))=, also =&labels= :: Function bindings.  These have no value form. =&labels= allows the function to refer to itself -- note that since =let+= is always recursive, this is the only difference between the two forms.  Example:
#+BEGIN_SRC lisp
(let+ (((&flet add2 (x)
          (+ x 2))))
  (add2 5))  ; => 7
#+END_SRC

- =((&plist (variable key [default])*)=, also =&plist-r/o= :: Access to property lists.  When =key= is not given, =variable= is used instead, and =default= 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 uses =symbol-macrolet=).  Example:
#+BEGIN_SRC lisp
(let+ (((&plist a (my-b b) (c nil 3)) '(a 1 b 2)))
  (list a my-b c))  ; => (1 2 3)
#+END_SRC

- =(((&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.

** Nesting

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,
#+BEGIN_SRC lisp
(let+ ((#((&complex a b)) (vector (complex 1 2))))
  (list a b))
#+END_SRC
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.

** Convenience macros

- =(defun+ name (argument*) form*)=, also =(lambda (argument*) form*)= :: Work like =defun= and =lambda=, but arguments are destructured using =let+=.  Example:
#+BEGIN_SRC lisp
(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)
#+END_SRC
See also =&labels+= and =&lambda+=.

- =define-structure-let+= :: Can be used to provide destructuring forms for structures.

** Other forms

- =(&once-only symbols ...)= and =(&with-gensyms symbols)= are useful for writing macros.

* Extensions

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:

#+BEGIN_SRC lisp
(define-let+-expansion (&complex (x y))
  "Access real and imaginary part of the value.  Read-only."
  `(let ((,x (realpart ,value))
         (,y (imagpart ,value)))
     ,@body))
#+END_SRC
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) and
  =body= (the body inside the =let+= form), you can customize both of
  this using keyword arguments,
- unless required otherwise, =value= is wrapped in =once-only=
  preventing multiple evaluations of the same form.  See the arguments =:uses-value?= and =:once-only?= for =define-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=.

* Reporting bugs

Please open an [[https://github.com/sharplispers/let-plus/issues][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.
