[R7RS base] Creates a local scope where var … are bound to the value of expr …, then evaluates body …. Vars must be symbols, and there shouldn’t be duplicates. The value(s) of the last expression of body … becomes the value(s) of this form.
The four forms differ in terms of the scope and the order
exprs are evaluated.
Let
evaluates exprs before (outside of) let
form.
The order of evaluation of exprs is undefined, and the compiler
may reorder those exprs freely for optimization.
Let*
evaluates exprs, in the order they appears,
and each expr is evaluated in the scope
where vars before it are bound.
Letrec
evaluates exprs, in an undefined order,
in the environment where vars are already bound
(to an undefined value, initially).
letrec
is necessary to define mutually recursive local procedures.
Finally, letrec*
uses the same scope rule as letrec
,
and it evaluates expr in the order of appearance.
(define x 'top-x) (let ((x 3) (y x)) (cons x y)) ⇒ (3 . top-x) (let* ((x 3) (y x)) (cons x y)) ⇒ (3 . 3) (let ((cons (lambda (a b) (+ a b))) (list (lambda (a b) (cons a (cons b 0))))) (list 1 2)) ⇒ (1 2 . 0) (letrec ((cons (lambda (a b) (+ a b))) (list (lambda (a b) (cons a (cons b 0))))) (list 1 2)) ⇒ 3
You need to use letrec*
if evaluation of one expr requires
the value of var that appears before the expr. In the following
example, calculating the value of a and b requires the value
of cube, so you need letrec*
. (Note the difference from
the above example, where calculating the value of list
doesn’t need to take the value of cons bound in the same letrec
.
The value of cons isn’t required until list is actually applied.)
(letrec* ((cube (lambda (x) (* x x x))) (a (+ (cube 1) (cube 12))) (b (+ (cube 9) (cube 10)))) (= a b)) ⇒ #t
This example happens to work with letrec
in the current Gauche, but
it is not guaranteed to keep working in future. You just should not
rely on evaluation order when you use letrec
.
In retrospect, it would be a lot simpler if we only had letrec*
.
Unfortunately letrec
preceded for long time in Scheme history
and it’s hard to remove that. Besides, letrec
does have more
opportunities to optimize than letrec*
.
A convenient macro when you have only one variable. Expanded as follows.
(let ((var expr)) body ...)
This macro simplifies the following idiom:
(let1 var expr (if var then else))
This macro simplifies the following idiom:
(let1 var expr body ... var)
[SRFI-2]
In short, it works like let*
, but returns #f
immediately
whenever the expression in bindings evaluates to #f
.
Each binding should be one of the following form:
(variable expression)
The expression is evaluated; if it yields true value, the value
is bound to variable, then proceed to the next binding. If
no more bindings, evaluates body …. If expression
yields #f
, stops evaluation and returns #f
from and-let*
.
(expressionx)
In this form, variable is omitted. Expression is evaluated and the result is used just to determine whether we continue or stop further evaluation.
bound-variable
In this form, bound-variable should be an identifier denoting
a bound variable. If its value is not #f
, we continue
the evaluation of the clauses.
Let’s see some examples. The following code searches key from an assoc-list alist and returns its value if found.
(and-let* ((entry (assoc key alist))) (cdr entry))
If arg is a string representation of an exact integer, returns its value; otherwise, returns 0:
(or (and-let* ((num (string->number arg)) ( (exact? num) ) ( (integer? num) )) num) 0)
The following is a hypothetical code that searches a certain server port number from a few possibilities (environment variable, configuration file, ...)
(or (and-let* ((val (sys-getenv "SERVER_PORT"))) (string->number val)) (and-let* ((portfile (expand-path "~/.server_port")) ( (file-exists? portfile) ) (val (call-with-input-string portfile port->string))) (string->number val)) 8080) ; default
Evaluates test, and if it isn’t #f
, binds var to it
and evaluates exp1 exp2 …. Returns the result(s) of
the last expression. If test evaluates to #f
, returns #f
.
This can be easily written by and-let*
or if-let1
as follows.
However, we’ve written this idiom so many times that it deserves
another macro.
(and-let1 var test exp1 exp2 ...) ≡ (and-let* ([var test]) exp1 exp2 ...) ≡ (if-let1 var test (begin exp1 exp2 ...) #f)
A macro that emulates dynamic scoped variables.
Vars must be variables bound in the scope including
fluid-let
form. Vals are expressions.
Fluid-let
first evaluates vals, then
evaluates body …, with binding
vars to the corresponding values during the dynamic
scope of body ….
Note that, in multithreaded environment, the change of the value of vars are visible from all the threads. This form is provided mainly for the porting convenience. Use parameter objects instead (see Parameters) for thread-local dynamic state.
(define x 0) (define (print-x) (print x)) (fluid-let ((x 1)) (print-x)) ⇒ ;; prints 1
[SRFI-8] This is the way to receive multiple values. Formals can be a (maybe-improper) list of symbols. Expression is evaluated, and the returned value(s) are bound to formals like the binding of lambda formals, then body … are evaluated.
(define (divrem n m) (values (quotient n m) (remainder n m))) (receive (q r) (divrem 13 4) (list q r)) ⇒ (3 1) (receive all (divrem 13 4) all) ⇒ (3 1) (receive (q . rest) (divrem 13 4) (list q rest)) ⇒ (3 (1))
See also call-with-values
in Multiple values
which is the procedural equivalent of receive
.
You can use define-values
(see Definitions) to
bind multiple values to variables simultaneously.
Also let-values
and let*-values
below
provides let
-like syntax with multiple values.
[R7RS base]
vars are a list of variables. expr is evaluated, and
its first return value is bound to the first variable in vars,
its second return value to the second variable, and so on, then
body is evaluated.
The scope of exprs are the outside of let-values
form,
like let
.
(let-values (((a b) (values 1 2)) ((c d) (values 3 4))) (list a b c d)) ⇒ (1 2 3 4) (let ((a 1) (b 2) (c 3) (d 4)) (let-values (((a b) (values c d)) ((c d) (values a b))) (list a b c d))) ⇒ (3 4 1 2)
vars can be a dotted list or a single symbol, like the lambda parameters.
(let-values (((x . y) (values 1 2 3 4))) y) ⇒ (2 3 4) (let-values ((x (values 1 2 3 4))) x) ⇒ (1 2 3 4)
If the number of values returned by expr doesn’t match what vars expects, an error is signaled.
[R7RS base]
Same as let-values
, but each expr’s scope includes
the preceding vars.
(let ((a 1) (b 2) (c 3) (d 4)) (let*-values (((a b) (values c d)) ((c d) (values a b))) (list a b c d))) ⇒ (3 4 3 4)
[SRFI-31] A macro to evaluate an expression with recursive reference.
In the first form, evaluates expr while var in expr is bound to the result of expr. The second form is equivalent to the followings.
(rec name (lambda vars expr ...))
Some examples:
;; constant infinite stream (rec s (cons 1 (delay s))) ;; factorial function (rec (f n) (if (zero? n) 1 (* n (f (- n 1)))))