One difference: Internal defines are in a mutually recursive scope, but let bindings are not.
This means than in a let
:
(let ([x expr-1] [y expr-2])
body)
The expr-1
and expr-2
cannot refer to x
or y
. More concretely,
(let ([x (stream-cons 1 y)] [y (stream-cons 2 x)])
x)
;error=> y: unbound identifier in: y
And if x
or y
is defined outside of the let
, expr-1 and expr-2 will refer to the outer definitions, and not the ones introduced by the let. Concretely:
(define x 'outer)
(let ([x 'inner] [y x]) ; <- this x refers to outer,
y) ; so y is 'outer
;=> 'outer
However, internal defines have a mutually recursive scope, which means that in
(block
(define x expr-1)
(define y expr-2)
body)
The expr-1
and expr-2
can refer to x
or y
. Concretely,
(require racket/block)
(block
(define x (stream-cons 1 y))
(define y (stream-cons 2 x))
(stream->list (stream-take x 5)))
;=> (list 1 2 1 2 1)
The Scope of a define
....A....
(define (f)
(define t1 ..B..)
(define x ..C..)
(define t2 ..D..)
....E....)
....F....
The x
is visible everywhere in the body of f
, but not outside that. That means it's visible in B
, C
, D
, and E
, but not in A or F.
The Scope of a let
....A....
(define (f)
(let ([t1 ..B..]
[x ..C..]
[t2 ..D..])
....E....))
....F....
Here the x
is visible everywhere in the body of the let
, but not outside that. That means it's visible in E
, but not in A, B, C, D, or F.
The Scope of a let*
....A....
(define (f)
(let* ([t1 ..B..]
[x ..C..]
[t2 ..D..])
....E....))
....F....
Here the x
is visible everywhere in the body of the let*
and in let*
bindings that come after it, but not outside that. That means it's visible in D
and E
, but not in A, B, C, or F.
The Scope of a letrec
....A....
(define (f)
(letrec ([t1 ..B..]
[x ..C..]
[t2 ..D..])
....E....))
....F....
The x
is visible everywhere in the body of the letrec
and in the bindings of the letrec
, but not outside that. That means it's visible in B
, C
, D
, and E
, but not in A or F.
The scope of variables in letrec
and the scope of local define
variables are very similar because both letrec
and define
work with mutually recursive scopes.
define
s are in a mutually recursive scope (including mut-rec functions),let
bindings are not, but there are also other style reasons – Chiller(let ([x expr-1] [y expr-2]) body)
, expr-1 and expr-2 cannot refer tox
ory
. . . . . . . However, in(block (define x expr-1) (define y expr-2) body)
, both expr-1 and expr-2 can have references tox
andy
. – Chillerdefine
s are only "allowed" on the top level. When used in local definition context, them are just syntax sugar and will be expanded into appropriatelet
form. – Amaryllis