Skip to main content

let, let

let, let*∗ Special Operator*

Syntax:

let ({var | (var [init-form])}*) {declaration}* {form}* → {result}*

let* ({var | (var [init-form])}*) {declaration}* {form}* → {result}*

Arguments and Values:

var—a symbol.

init-form—a form.

declaration—a declare expression; not evaluated.

form—a form.

results—the values returned by the forms.

Description:

let and let* create new variable bindings and execute a series of forms that use these bindings. let performs the bindings in parallel and let* does them sequentially.

The form

(let ((var1 init-form-1 )

(var2 init-form-2 )

...

(varm init-form-m))

declaration1

declaration2

...

declarationp

form1

form2

...

formn)

let, let*∗*

first evaluates the expressions init-form-1, init-form-2, and so on, in that order, saving the resulting values. Then all of the variables varj are bound to the corresponding values; each binding is lexical unless there is a special declaration to the contrary. The expressions formk are then evaluated in order; the values of all but the last are discarded (that is, the body of a let is an implicit progn).

let* is similar to let, but the bindings of variables are performed sequentially rather than in parallel. The expression for the init-form of a var can refer to vars previously bound in the let*.

The form

(let* ((var1 init-form-1 )

(var2 init-form-2 )

...

(varm init-form-m))

declaration1

declaration2

...

declarationp

form1

form2

...

formn)

first evaluates the expression init-form-1, then binds the variable var1 to that value; then it evaluates init-form-2 and binds var2, and so on. The expressions formj are then evaluated in order; the values of all but the last are discarded (that is, the body of let* is an implicit progn).

For both let and let*, if there is not an init-form associated with a var, var is initialized to nil.

The special form let has the property that the scope of the name binding does not include any initial value form. For let*, a variable’s scope also includes the remaining initial value forms for subsequent variable bindings.

Examples:

(setq a ’top) → TOP
(defun dummy-function () a) → DUMMY-FUNCTION
(let ((a ’inside) (b a))
(format nil "~S ~S ~S" a b (dummy-function))) → "INSIDE TOP TOP"
(let\* ((a ’inside) (b a))
(format nil "~S ~S ~S" a b (dummy-function))) → "INSIDE INSIDE TOP"
(let ((a ’inside) (b a))
(declare (special a))
(format nil "~S ~S ~S" a b (dummy-function))) → "INSIDE TOP INSIDE"
The code
(let (x)
Data and Control

(declare (integer x))
(setq x (gcd y z))
...)
is incorrect; although x is indeed set before it is used, and is set to a value of the declared type *integer* , nevertheless x initially takes on the value **nil** in violation of the type declaration.

See Also:

progv

Expanded Reference: let, let*​

Basic variable binding with let​

let creates local variable bindings. Each variable is bound to the result of evaluating its init-form, then the body forms are executed.

(let ((x 10)
(y 20))
(+ x y))
=> 30

Variables without init-forms default to NIL​

If no init-form is provided, the variable is initialized to nil.

(let (x y z)
(list x y z))
=> (NIL NIL NIL)

Parallel binding with let​

In let, all init-forms are evaluated before any variable is bound. Here, b gets the outer value of a, not the inner one.

(let ((a 1))
(let ((a 2)
(b a))
(list a b)))
=> (2 1)

Sequential binding with let*​

In let*, each binding is completed before the next init-form is evaluated. Here, b sees the new value of a.

(let* ((a 1)
(b (+ a 10))
(c (+ a b)))
(list a b c))
=> (1 11 12)

Nested let for building up local context​

let forms can be nested and the body is an implicit progn, returning the value of the last form.

(let ((name "Alice")
(greeting "Hello"))
(let ((message (concatenate 'string greeting ", " name "!")))
(length message)
message))
=> "Hello, Alice!"

Shadowing outer bindings​

Inner let bindings shadow outer ones without affecting them.

(let ((x 100))
(list
(let ((x 1)) x)
x))
=> (1 100)