Racket has no while keyword or break statement of its own. A loop is a recursive call in tail position, or a for form that walks a sequence. Other control flow is done with exceptions, escape continuations and prompts, all of which are ordinary procedures and forms.
Tail calls and recursion
A call in tail position (the last expression of a procedure body, of the branch of an if, cond or case, of and and or, and of when and unless) does not add to the stack, so a procedure that calls itself in tail position runs in constant space however long it loops. A named let is the usual spelling of such a loop:
(define (sum-to n)
(let loop ([i 1] [total 0])
(if (> i n)
total
(loop (add1 i) (+ total i)))))
do loops also exist: (do ([i 0 (add1 i)]) ((= i 3)) (displayln i)).The for family
(for (for-clause …) body-or-break … body) runs the body once for each combination of elements that the clauses produce. A clause [id seq-expr] walks one sequence; [(id …) seq-expr] binds the several values each element supplies ([(k v) (in-hash h)]). In for the clauses walk their sequences in parallel, stopping at the shortest one; for* nests them, so the later clauses run once for each earlier element.
| Clause | Meaning |
|---|---|
[id seq], [(id …) seq] | bind the element (or its several values) |
#:when guard | skip the rest of the body (and the following clauses) for this element unless the guard is true |
#:unless guard | skip unless the guard is false |
#:break guard | stop the whole loop, before this element, when the guard is true; allowed among the clauses and among the body forms |
#:final guard | like #:break, but finish the current iteration first |
#:do [do-body …] | run expressions at that point in the nesting |
#:splice (splicing-id . form) | splice a form that expands into more clauses |
#:on-length-mismatch expr | what to do when parallel sequences have different lengths |
A #:when, #:unless, #:do, #:break or #:final clause splits the clauses: the sequences before it are walked in parallel, and the ones after it nest inside. Each variant below collects the bodies' results in a different way; every one has a * form (for*/list).
| Form | Result |
|---|---|
for | void; the body is run for its effect |
for/list, for/vector, for/lists | a list, a vector (#:length n and #:fill v choose a size), or several lists at once |
for/hash, for/hasheq, for/hasheqv, for/hashalw | an immutable hash table; the body returns two values, a key and a value |
for/sum, for/product | the sum or product of the numbers the body returns |
for/and, for/or | the first false value (or #t); the first true value (or #f) |
for/first, for/last | the first or last body value, or #f |
for/fold, for/foldr | threads accumulators through the loop: (for/fold ([acc 0]) ([x xs]) (+ acc x)); #:result expr after the accumulators computes the final value from them |
for/set, for/seteq, for/mutable-set, for/stream, for/async, for/list/concurrent and others | sets (racket/set), lazy streams (racket/stream), and further collectors from other libraries |
for/fold/derived, for/foldr/derived, define-sequence-syntax | the tools for writing a new for form or a new sequence |
(for ([x '(a b c)] [i (in-naturals 1)]) ; parallel
(printf "~a: ~a\n" i x))
(for*/list ([x (in-range 3)] [y (in-range x)]) ; nested
(cons x y)) ; => '((1 . 0) (2 . 0) (2 . 1))
(for/hash ([w '("a" "bb" "c")]) (values w (string-length w)))
(for/fold ([sum 0] #:result (* 2 sum)) ([x (in-list '(1 2 3))])
(+ sum x)) ; => 12
(for/list ([x (in-range 10)] #:when (even? x) #:break (> x 6)) x) ; => '(0 2 4 6)
Sequences
A sequence is anything a for clause can walk. A list, mutable list, vector, flvector, fxvector, string, byte string, hash table (it supplies two values, key and value), dictionary, set, stream, input port, non-negative integer n (like (in-range n)) and any value with the prop:sequence property are all sequences. A bare sequence in a clause works, but a specific constructor is faster and says what is expected:
| Constructor | Walks |
|---|---|
in-range, in-inclusive-range, in-naturals | numbers: (in-range start end step); in-inclusive-range includes the end; in-naturals counts up without end |
in-list, in-mlist, in-vector, in-string, in-bytes | the elements of a list, a mutable list, a vector, a string, a byte string |
in-hash, in-hash-keys, in-hash-values, in-hash-pairs | a hash table's entries as two values, its keys, its values, or its pairs (also in-mutable-hash, in-immutable-hash, in-weak-hash and the -keys, -values, -pairs forms of each) |
in-set, in-stream, in-dict, in-dict-keys, in-dict-values, in-dict-pairs | the elements of a set, stream or dictionary |
in-lines, in-bytes-lines, in-port, in-input-port-bytes, in-input-port-chars | the lines, items, bytes or characters of an input port |
in-directory | the files below a directory, recursively |
in-value, in-values-sequence, in-values*-sequence | a single value, or a sequence of several values per element |
in-parallel, in-sequences, in-cycle, in-indexed, in-slice, in-syntax, in-generator, in-producer | combine, chain, repeat, number or chunk other sequences; walk a syntax list; a generator; a procedure called until it returns a stop value |
stop-before, stop-after | end a sequence at the first element that satisfies a predicate |
The library racket/sequence has sequence-map, sequence-filter, sequence-fold, sequence-ref, sequence-length, sequence-append, sequence->list and more, and racket/stream has lazy streams (stream-cons, stream-first, stream-rest, stream-map, stream-filter, stream-take).
Multiple values
A procedure can return several values: (values 1 2). A context that expects one value raises an error if it gets another number. (let-values ([(a b) (values 1 2)]) …), (define-values (a b) …), (call-with-values thunk receiver), match-define with (values …) and for clauses like [(k v) …] receive them. (values) returns no value at all. Several built-in procedures return multiple values, such as quotient/remainder, split-at and partition.
Exceptions
An exception is any value passed to raise; usually it is a structure of type exn, made by error and the raise-… functions. (with-handlers ([predicate handler] …) body …+) runs the body; if it raises a value that a predicate accepts, the handler is called with it, after the body's dynamic extent has been left, and its result is the result of the whole form. with-handlers* calls the handler in tail position with respect to the form.
| Raise | Use |
|---|---|
(error 'who "message ~a" v …), (error "message" v …), (error 'sym) | an exn:fail with a message. A lone symbol gives error: sym. A string first is the message and each value after it is added, after a space. A symbol and then a string makes the string a format string, and the symbol, a prefix sym: , names the source |
(raise-user-error …) | an exn:fail:user: an error to report without a stack trace |
(raise-argument-error 'name "expected" v), raise-arguments-error, raise-result-error, raise-range-error, raise-type-error, raise-arity-error, raise-mismatch-error | the errors that library functions give for a wrong argument or result |
(raise-syntax-error 'name "message" stx) | an exn:fail:syntax that points at a piece of syntax; used by macros |
(raise v) | any value; raise does not require an exn |
The structure types of the exceptions that Racket itself raises form this tree (each name is the type and the predicate adds ?):
| Type | Raised for |
|---|---|
exn | the root; fields message and continuation-marks |
exn:fail | errors a program can usually handle |
exn:fail:contract | a wrong argument or a broken contract. Subtypes: :arity, :divide-by-zero, :non-fixnum-result, :continuation, :variable (an unbound or undefined variable) |
exn:fail:syntax | a syntax error. Subtypes: :unbound, :missing-module |
exn:fail:read | a reader error. Subtypes: :eof, :non-char |
exn:fail:filesystem | a file error. Subtypes: :exists, :version, :errno, :missing-module |
exn:fail:network | a network error. Subtype: :errno |
exn:fail:out-of-memory, exn:fail:unsupported, exn:fail:user | memory limit reached; an unsupported operation; an error raised by raise-user-error |
exn:break | an interrupt (Ctrl-C). Subtypes: :hang-up, :terminate. It is not an exn:fail, so (with-handlers ([exn:fail? …]) …) does not catch it |
(with-handlers ([exn:fail:contract:divide-by-zero?
(lambda (e) 'infinity)]
[exn:fail? (lambda (e) (exn-message e))])
(/ 1 0)) ; => 'infinity
(with-handlers ([symbol? values]) ; any value can be raised
(raise 'oops)) ; => 'oops
(dynamic-wind
(lambda () (displayln "enter"))
(lambda () (error "oops")) ; the post thunk runs on the way out
(lambda () (displayln "leave")))
dynamic-wind.When no handler takes an exception, the uncaught-exception handler prints the message through the error display handler and, in a module run by racket, the program ends. (error-display-handler), (error-escape-handler) and (uncaught-exception-handler) can be set to change that. (exn-message e) and (exn-continuation-marks e) read an exn.
Continuations and prompts
(let/ec k body …)and(call/ec proc)(call-with-escape-continuation) givek, a procedure that jumps out of the body and returns its argument as the value of the form. It can be used only while the body runs, and it is cheap. It is how Racket programs return early:(let/ec return (for ([x xs]) (when (bad? x) (return x))) #f).(let/cc k body …)andcall/cccapture the full continuation up to the nearest prompt; callingklater replaces the current continuation, up to that prompt, with the saved one. Racket distinguishes prompts (call-with-continuation-prompt,abort-current-continuation,make-continuation-prompt-tag), composable continuations (call-with-composable-continuation) and continuation barriers.racket/controladdsprompt,control,shift,resetand relatives for delimited control.racket/generatormakes generators on top of continuations:(generator () (yield 1) (yield 2)), called repeatedly to get 1, 2, then the finishing value.(with-continuation-mark key value body)andcontinuation-mark-set-firstattach values to stack frames; parameters (below) are implemented with them.(dynamic-wind pre thunk post)runspreandpostevery time control enters and leaves the thunk, by return, by exception or by continuation jump.
Promises and parameters
(delay expr) makes a promise that computes expr the first time (force p) is called; lazy and racket/promise's delay/name, delay/strict, delay/sync, delay/thread and delay/idle vary when and where it runs. #lang lazy makes every function call lazy.
A parameter is a procedure that holds a dynamically scoped value: (define current-indent (make-parameter 0)), read by (current-indent), changed for the extent of a body by (parameterize ([current-indent 2]) …), and set outright by (current-indent 3). Racket's own settings are parameters: current-output-port, current-directory, current-command-line-arguments, error-print-width, current-namespace, read-case-sensitive. Each thread starts with the parameter values of the thread that made it.
Not described
Known, not yet covered
- Reference: the full argument lists and the many other
forvariants,racket/generatorandracket/controlbeyond the names given, the details of continuation barriers, the stack behaviour ofwith-handlersagainstcall-with-exception-handler, andexn:breakhandling withbreak-enabledwere not described. Parts of the Reference chapters on control, exceptions and for (sections read in part) are summarised. - Reference: what a bare input port yields as a sequence (bytes or characters) is not stated; use
in-port(which reads withreadby default),in-input-port-bytes,in-input-port-charsorin-linesto be explicit.
See also
- Functions – procedures, arity and keyword arguments
- Concurrency – threads, places and futures
- Scheme: continuations and dynamic-wind – the Scheme view of continuations