SRFI 5: A compatible let form with signatures and rest arguments
|(require srfi/5)||package: srfi-lib|
Original specification: SRFI 5
For historical reasons, the SRFI 5 specification document has a restrictive license and is not included in the main Racket distribution.
The implementation in srfi/5 and this documentation are distributed under the same license as Racket: only the original specification document is restrictively licensed.
(let ([id init-expr] ...) body ...+)
(let ([id init-expr] ...+ rest-binding) body ...+)
(let loop-id ([id init-expr] ... maybe-rest-binding) body ...+)
(let (loop-id [id init-expr] ... maybe-rest-binding) body ...+)
| rest-binding rest-binding = rest-id rest-init-expr ...
As with let from racket/base, SRFI 5’s let form conceptually expands to the immediate application of a function to the values of the init-exprs: the ids are bound in the bodys (but not in any init-exprs or rest-init-exprs), and loop-id, if present, is bound in the bodys to the function itself, allowing it to be used recursively. An id or a rest-id can shadow loop-id, but the rest-id (if given) and all iss much be distinct.
SRFI 5’s let adds support for a syntax like define’s function shorthand, which allows the bindings to be written in a syntax resembling an application of the function bound to loop-id.
Additionally, SRFI 5’s let adds support for rest arguments. If a rest-id is present, the function bound to loop-id (or the conceptual anonymous function, if loop-id is not used) will accept an unlimited number of additional arguments after its required by-position arguments, and the rest-id will be bound in the bodys (but not in any init-exprs or rest-init-exprs) to a list of those additional arguments. The values of the rest-init-exprs are supplied as arguments to the initial, implicit application when the let form is evaluated, so the initial value bound to rest-id is (list rest-init-expr ...).
Unlike the kw-formals of lambda and define or the formals of case-lambda, the bindings of SRFI 5’s let, with or without a rest-binding, are always a proper (syntactic) list.
A rest-binding can be used with both the define-like and the named-let–like variants of let. It is also possible to use rest-id without any loop-id; however, as specified in the grammar, at least one id–init-expr pair is required in that case. (Otherwise, there would be an ambiguity with the define-like variant).
; define-like bindings
> (define (factorial n) (let (fact [n n] [acc 1]) (if (zero? n) acc (fact (sub1 n) (* n acc))))) > (factorial 5)
> (factorial 11)
; rest arguments with named-let--like bindings
> (let reverse-onto ([lst '(a b c)] tail) (if (null? lst) tail (apply reverse-onto (cdr lst) (car lst) tail)))
'(c b a)
> (let reverse-onto ([lst '(a b c)] tail 'x 'y 'z) (if (null? lst) tail (apply reverse-onto (cdr lst) (car lst) tail)))
'(c b a x y z)
> (let no-evens (lst 1 2 3 4 5) (cond [(null? lst) '()] [(even? (car lst)) (apply no-evens (cdr lst))] [else (cons (car lst) (apply no-evens (cdr lst)))]))
'(1 3 5)
; rest arguments with define-like bindings
> (let (reverse-onto [lst '(a b c)] tail) (if (null? lst) tail (apply reverse-onto (cdr lst) (car lst) tail)))
'(c b a)
> (let (reverse-onto [lst '(a b c)] tail 'x 'y 'z) (if (null? lst) tail (apply reverse-onto (cdr lst) (car lst) tail)))
'(c b a x y z)
> (let (loop [continue? 0] args 'a 'a1 'a2) (case continue? [(0) (cons args (loop 1 'b))] [(1) (cons args (loop 2 'c 'd))] [else (list args)]))
'((a a1 a2) (b) (c d))
; rest arguments without any loop-id
> (let ([x 1] [y 2] z 3 4 5 6 7) (list* x y z))
'(1 2 3 4 5 6 7)