define vs let in Racket
Short answer
define binds a name in the enclosing body — module level or inside a function. let creates a binding visible only inside its own body.
(define pi 3.14159) ; visible for the rest of the module
(define (area r)
(define r2 (* r r)) ; visible for the rest of this function
(* pi r2))
(define (area-let r)
(let ([r2 (* r r)]) ; visible only inside the let
(* pi r2)))Internal define is generally preferred in modern Racket — it reads more linearly and avoids a level of nesting.
#let, let* and letrec
(let ([a 1]
[b (+ a 1)]) ; ERROR — a is not visible yet
b)
(let* ([a 1]
[b (+ a 1)]) ; fine — sequential
b)
(letrec ([even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))]
[odd? (lambda (n) (if (= n 0) #f (even? (- n 1))))])
(even? 10)) ; fine — mutually recursiveletevaluates all its bindings in the outer scope, simultaneously.let*evaluates them in order, each seeing the previous ones.letreclets them refer to each other, which is what recursion needs.
Use let* when in doubt; the sequential behaviour is what people usually expect.
#Shadowing
(define x 10)
(let ([x 20])
x) ; 20
x ; 10 — unchangedThe binding is scoped to the let, which is exactly the property that makes it safe.