Binding
Bindings in Lisp
Single binding
When we wish to bind a variable locally we can use let.
(let ((x 3)) (+ x 4))
The scope of the binding of x is until the end of the let form.
Note the apparently extra pair of parens around the binding. That is to facilitate several bindings at once.
Reasons why we might like to introduce such bindings are
- to make our code more readable,
- to perform our computation in stages, or
- to compute once and use several times.
(defun roots (a b c) (let ((discriminant (- (* b b) (* 4 a c)))) (values (/ (+ (- b) (sqrt discriminant)) (* 2 a)) (/ (- (- b) (sqrt discriminant)) (* 2 a))))) (roots 1 -3 2)
2.0 1.0
Several bindings
We can bind several variables.
(let ((x 3) (y 5)) (* y (+ x 4)))
Subsequent bindings
Note that the scope of the x binding in let does not include the y binding so
we can not do this.
(let ((x 3)(y (+ x 2))) ;(* y (+ x 4)))
The variable X is unbound. [Condition of type UNBOUND-VARIABLE]
For this kind of scope we can use let*
(let* ((x 3) (y (+ x 2))) (* y (+ x 4)))
With let* the scope of each binding includes all subsequent bindings.
Multiple value bindings
Some Lisp functions return more than one value.
(floor 9 4)
We can bind multiple values using multiple-value-bind
(multiple-value-bind (quotient remainder) (floor 9 4) (format nil "The quotient is ~d and the remainder is ~d." quotient remainder))
The variables quotient and remainder are bound to the two values returned by floor.
Destructuring
Sometimes we want to destructure values. For example, we might have the list
(3 4) and we wish to assign 3 to x and 4 to y. We can do this with
destructuring-bind
(destructuring-bind (x y) '(3 4) (format nil "X is ~d and Y is ~d." x y))
Destructuring-bind is more flexible. In fact it can bind anything that can
appear in a lambda list (i.e. in the argument list of a function definition).
In fact, it is used for just that purpose.
(let ((y (cons (list 'alpha) '(1 2 3)))) (destructuring-bind ((a &optional (b 'bee)) c d e) y (format t "~&Y: ~a~&" y) (format t "~&A: ~a, B: ~a, C: ~a, D: ~a, E: ~a.~&" a b c d e)))
As well as &optional it can also handle &key and some other more exotic terms.
Metabang-bind
There are times that we want to combine all of the above various kinds of bindings and even some others. For example, we might want to bind to slots of an object. The metabang-bind library covers all of these cases elegantly.
(ql:quickload :metabang-bind) (use-package :metabang-bind)
Now we can use bind just as we use let*. That is to say, unlike let, we can use
variables already bound in bind in subsequent bindings.
(defun roots (a b c) (bind ((discriminant (- (* b b) (* 4 a c))) (sqrt-discriminant (sqrt discriminant))) (values (/ (+ (- b) sqrt-discriminant) (* 2 a)) (/ (- (- b) sqrt-discriminant) (* 2 a))))) (roots 1 -3 2)
2.0 1.0
Bind also allows us to bind multiple values.
(bind (((:values quotient remainder) (floor 9 4))) (format nil "The quotient is ~d and the remainder is ~d." quotient remainder))
The quotient is 2 and the remainder is 1.
Note two things. On the one hand bind is shorter than multiple-value-bind but
on the other we need to write more parens to indicate the :values
- Structures
Bindcan also bind to fields of a data structure(deftype bt () '(or node null)) (defstruct node (value nil :type fixnum) (left nil :type bt) (right nil :type bt)) (bind ((n (make-node :value 3 :left (make-node :value 2) :right (make-node :value 4))) ((:structure node- value left right) n)) (list value left right))
(3 #S(NODE :VALUE 2 :LEFT NIL :RIGHT NIL) #S(NODE :VALUE 4 :LEFT NIL :RIGHT NIL))
- Classes
Bindcan also destructure the slots of an instance of a class.(defclass cnode () ((value :initarg :value :accessor cnode-value) (left :initarg :left :accessor cnode-left) (right :initarg :right :accessor cnode-right))) (bind ((n (make-instance 'cnode :value 3 :left (make-instance 'cnode :value 2) :right (make-instance 'cnode :value 4))) ((:slots value left right) n)) (list value left right))
(3 #<CNODE {12019BE263}> #<CNODE {1201A4A813}>)
- Other forms
With
bindyou can also destructure vectors and arrays of all dimensions.(bind ((#(x y z) #(5 6 7))) (+ x y z))
If you use the mmontone version of
metabang-bind(currently not available through quicklisp) , you can use regular expressions in the style of the cl-ppcre library by loading:metabang-bind-ppcre.(ql:quickload :metabang-bind-ppcre) (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" fname lname nil month year) "Frank Zappa 21.12.1940")) (list fname lname month year))
Several other, more exotic forms of binding are also available and
bindis extensible; you can add your own binding forms. Consult the documentation.