An open API service indexing awesome lists of open source software.

https://github.com/tpapp/let-plus

destructuring extension of let*
https://github.com/tpapp/let-plus

Last synced: about 1 year ago
JSON representation

destructuring extension of let*

Awesome Lists containing this project

README

          

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

*IMPORTANT* This library is [[https://tpapp.github.io/post/orphaned-lisp-libraries/][unmaintained]].

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/tpapp/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.