3.18 Iterations and Comprehensions: for, for/list, ... (original) (raw)
3.18 Iterations and Comprehensions: for, for/list, ...🔗ℹ
Iterations and Comprehensions in The Racket Guide introduces iterations and comprehensions.
The for iteration forms are based on SRFI-42 [SRFI-42].
3.18.1 Iteration and Comprehension Forms🔗ℹ
(for (for-clause ...) body-or-break ... body) for-clause = [id seq-expr] | [(id ...) seq-expr] #:when guard-expr #:unless guard-expr #:do [do-body ...] break-clause #:splice (splicing-id . form) break-clause = #:break guard-expr #:final guard-expr body-or-break = body break-clause seq-expr : sequence?
Iteratively evaluates bodys. The for-clauses introduce bindings whose scope includes body and that determine the number of times that body is evaluated. A break-clause either among the for-clauses or bodys stops further iteration.
In the simple case, each for-clause has one of its first two forms, where [id seq-expr] is a shorthand for [(id) seq-expr]. In this simple case, the seq-exprs are evaluated left-to-right, and each must produce a sequence value (seeSequences).
The for form iterates by drawing an element from each sequence; if any sequence is empty, then the iteration stops, and# is the result of the for expression. Otherwise a location is created for each id to hold the values of each element; the sequence produced by a seq-expr must return as many values for each iteration as corresponding ids.
The ids are then bound in the body, which is evaluated, and whose results are ignored. Iteration continues with the next element in each sequence and with fresh locations for eachid.
A for form with zero for-clauses is equivalent to a single for-clause that binds an unreferenced id to a sequence containing a single element. All of the ids must be distinct according to bound-identifier=?.
If any for-clause has the form #:when guard-expr, then only the preceding clauses (containing no #:when, #:unless, or #:do) determine iteration as above, and the body is effectively wrapped as
using the remaining for-clauses. A for-clause of the form #:unless guard-expr corresponds to the same transformation with unless in place of when. A for-clause of the form #:do [do-body ...] similarly creates nesting and corresponds to
where the do-body forms may introduce definitions that are visible in the remaining for-clauses.
A #:break guard-expr clause is similar to a#:unless guard-expr clause, but when #:breakavoids evaluation of the bodys, it also effectively ends all sequences within the for form. A #:final guard-expr clause is similar to #:break guard-expr, but instead of immediately ending sequences and skipping thebodys, it allows at most one more element from each later sequence and at most one more evaluation of the followingbodys. Among the bodys, besides stopping the iteration and preventing later body evaluations, a#:break guard-expr or #:final guard-exprclause starts a new internal-definition context.
A #:splice (splicing-id . form) clause is replaced by the sequence of forms that are produced by expanding (splicing-id . form), where splicing-id is bound usingdefine-splicing-for-clause-syntax. The binding context of that expansion includes previous binding from any clause preceding both the #:splice form and a #:when,#:unless, #:do, #:break, or#:final form. The result of a #:splice expansion can include more #:splice forms to further interleave clause binding and expansion. Support for #:splice clauses is intended less for direct use in source for forms than for building new forms that expand to for.
In the case of list and stream sequences, thefor form itself does not keep each element reachable. If a list or stream produced by a seq-expr is otherwise unreachable, and if the for body can no longer reference anid for a list element, then the element is subject togarbage collection. The make-do-sequence sequence constructor supports additional sequences that behave like lists and streams in this way.
If a seq-expr is a quoted literal list, vector, exact integer, string, byte string, immutable hash, or expands to such a literal, then it may be treated as if a sequence transformer such asin-list was used, unless the seq-expr has a true value for the 'for:no-implicit-optimization syntax property; in most cases this improves performance.
Examples:
> (for ([i '(1 2 3)] [j "abc"] #:when (odd? i) [k #(#t #f)]) (display (list i j k))) (1 a #t)(1 a #f)(3 c #t)(3 c #f) (-1)(0)(1)(-2)(0)(2)(-3)(0)(3) > (for ([(i j) #hash(("a" . 1) ("b" . 20))]) (display (list i j))) (a 1)(b 20) (1 a #t)(1 a #f) (1 a #t)(1 a #f)(2 b #t) (1 a #t) here > (for ([i '()]) (error "doesn't get here"))
Changed in version 6.7.0.4 of package base: Added support for the optional second result.
Changed in version 7.8.0.11: Added support for implicit optimization.
Changed in version 8.4.0.2: Added #:do.
Changed in version 8.4.0.3: Added #:splice.
(for/list (for-clause ...) body-or-break ... body)
Iterates likefor, but that the last expression in the bodys must produce a single value, and the result of the for/listexpression is a list of the results in order. When evaluation of a body is skipped due to a #:whenor #:unless clause, the result list includes no corresponding element.
Examples:
> (for/list ([i '(1 2 3)] [j "abc"] #:when (odd? i) [k #(#t #f)]) (list i j k)) '((1 #\a #t) (1 #\a #f) (3 #\c #t) (3 #\c #f)) > (for/list ([i '(1 2 3)] [j "abc"] #:break (not (odd? i)) [k #(#t #f)]) (list i j k)) '((1 #\a #t) (1 #\a #f)) > (for/list () 'any) '(any) > (for/list ([i '()]) (error "doesn't get here")) '()
(for/vector maybe-length (for-clause ...) body-or-break ... body) maybe-length = | #:length length-expr #:length length-expr #:fill fill-expr length-expr : exact-nonnegative-integer?
Iterates like for/list, but results are accumulated into a vector instead of a list.
If the optional #:length clause is specified, the result oflength-expr determines the length of the result vector. In that case, the iteration can be performed more efficiently, and it terminates when the vector is full or the requested number of iterations have been performed, whichever comes first. Iflength-expr specifies a length longer than the number of iterations, then the remaining slots of the vector are initialized to the value of fill-expr, which defaults to 0 (i.e., the default argument of make-vector).
Examples:
The for/vector form may allocate a vector and mutate it after each iteration of body, which means that capturing a continuation during body and applying it multiple times may mutate a shared vector.
(for/hash (for-clause ...) body-or-break ... body) (for/hasheq (for-clause ...) body-or-break ... body) (for/hasheqv (for-clause ...) body-or-break ... body) (for/hashalw (for-clause ...) body-or-break ... body)
Like for/list, but the result is an immutable hash table; for/hash creates a table using equal? to distinguish keys, for/hasheq produces a table usingeq?, for/hasheqv produces a table usingeqv?, and for/hashalw produces a table usingequal-always?. The last expression in the bodys must return two values: a key and a value to extend the hash table accumulated by the iteration.
Example:
'#hash((1 . "1") (2 . "2") (3 . "3"))
Changed in version 8.5.0.3 of package base: Added the for/hashalw form.
(for/and (for-clause ...) body-or-break ... body)
Iterates likefor, but when last expression of body produces#f, then iteration terminates, and the result of thefor/and expression is #f. If the bodyis never evaluated, then the result of the for/andexpression is #t. Otherwise, the result is the (single) result from the last evaluation of body.
Examples:
> (for/and ([i '(1 2 3 "x")]) (i . < . 3)) #f > (for/and ([i '(1 2 3 4)]) i) 4 > (for/and ([i '(1 2 3 4)]) #:break (= i 3) i) 2 > (for/and ([i '()]) (error "doesn't get here")) #t
(for/or (for-clause ...) body-or-break ... body)
Iterates likefor, but when last expression of body produces a value other than #f, then iteration terminates, and the result of the for/or expression is the same (single) value. If the body is never evaluated, then the result of the for/or expression is#f. Otherwise, the result is #f.
Examples:
> (for/or ([i '(1 2 3 "x")]) (i . < . 3)) #t > (for/or ([i '(1 2 3 4)]) i) 1 > (for/or ([i '()]) (error "doesn't get here")) #f
(for/sum (for-clause ...) body-or-break ... body)
Iterates like for, but each result of the last bodyis accumulated into a result with +.
Example:
> (for/sum ([i '(1 2 3 4)]) i) 10
(for/product (for-clause ...) body-or-break ... body)
Iterates like for, but each result of the last bodyis accumulated into a result with *.
Example:
> (for/product ([i '(1 2 3 4)]) i) 24
(for/lists (id ... maybe-result) (for-clause ...) body-or-break ... body) maybe-result = | #:result result-expr
Similar to for/list, but the last body expression should produce as many values as given ids. The ids are bound to the reversed lists accumulated so far in the for-clauses andbodys.
If a result-expr is provided, it is used as with for/foldwhen iteration terminates; otherwise, the result is as many lists as supplied ids.
The scope of id bindings is the same as for accumulator identifiers in for/fold. Mutating a id affects the accumulated lists, and mutating it in a way that produces a non-list can cause a final reverse for each id to fail.
Examples:
> (for/lists (l1 l2 l3) ([i '(1 2 3)] [j "abc"] #:when (odd? i) [k #(#t #f)]) (values i j k)) '(1 1 3 3)'(#\a #\a #\c #\c)'(#t #f #t #f) > (for/lists (acc) ([x '(tvp tofu seitan tvp tofu)] #:unless (member x acc)) x) '(tvp tofu seitan) > (for/lists (firsts seconds #:result (list firsts seconds)) ([pr '((1 . 2) (3 . 4) (5 . 6))]) (values (car pr) (cdr pr))) '((1 3 5) (2 4 6))
Changed in version 7.1.0.2 of package base: Added the #:result form.
(for/first (for-clause ...) body-or-break ... body)
Iterates likefor, but after body is evaluated the first time, then the iteration terminates, and the for/firstresult is the (single) result of body. If thebody is never evaluated, then the result of thefor/first expression is #f.
Examples:
"2" > (for/first ([i '()]) (error "doesn't get here")) #f
(for/last (for-clause ...) body-or-break ... body)
Iterates likefor, but the for/last result is the (single) result of the last evaluation of body. If thebody is never evaluated, then the result of thefor/last expression is #f.
Examples:
"4" > (for/last ([i '()]) (error "doesn't get here")) #f
(for/fold ([accum-id init-expr] ... maybe-result) (for-clause ...) body-or-break ... body) maybe-result = | #:result result-expr
Iterates like for. Before iteration starts, theinit-exprs are evaluated to produce initial accumulator values. At the start of each iteration, a location is generated for each accum-id, and the corresponding current accumulator value is placed into the location. The last expression inbody must produce as many values as accum-ids, and those values become the current accumulator values. When iteration terminates, if a result-expr is provided then the result of thefor/fold is the result of evaluating result-expr (with accum-ids in scope and bound to their final values), otherwise the results of the for/fold expression are the accumulator values.
Examples:
10'(2 1.7320508075688772 1.4142135623730951 1) '(0 1 2 3 4)
The binding and evaluation order of accum-ids andinit-exprs follow the textual, left-to-right order relative to the for-clauses, except that (for historical reasons)accum-ids are not available in the for-clauses for the outermost iteration. The lifetimes of variables are not quite the same as the lexical nesting, however: the variable referenced by aaccum-id has a fresh location in each iteration.
Changed in version 6.11.0.1 of package base: Added the #:result form.
Changed in version 8.11.1.3: Changed evaluation order to match textual left-to-right order, including evaluating init-exprs before the firstfor-clause’s right-hand side and fixing shadowing ofaccum-id.
(for/foldr ([accum-id init-expr] ... accum-option ...) (for-clause ...) body-or-break ... body) accum-option = #:result result-expr | #:delay #:delay-as delayed-id #:delay-with delayer-id
Like for/fold, but analogous to foldr rather thanfoldl: the given sequences are still iterated in the same order, but the loop body is evaluated in reverse order. Evaluation of a for/foldrexpression uses space proportional to the number of iterations it performs, and all elements produced by the given sequences are retained until backwards evaluation of the loop body begins (assuming the element is, in fact, referenced in the body).
Examples:
Furthermore, unlike for/fold, the accum-ids are not bound within guard-exprs or body-or-break forms that appear before a break-clause.
While the aforementioned limitations make for/foldr less generally useful than for/fold, for/foldr provides the additional capability to iterate lazily via the #:delay, #:delay-as, and#:delay-with options, which can mitigate many of for/foldr’s disadvantages. If at least one such option is specified, the loop body is given explicit control over when iteration continues: by default, eachaccum-id is bound to a promise that, when forced, produces theaccum-id’s current value.
In this mode, iteration does not continue until one such promise is forced, which triggers any additional iteration necessary to produce a value. If the loop body is lazy in its accum-ids—that is, it returns a value without forcing any of them—then the loop (or any of its iterations) will produce a value before iteration has completely finished. If a reference to at least one such promise is retained, then forcing it will resume iteration from the point at which it was suspended, even if control has left the dynamic extent of the loop body.
Examples:
This extra control over iteration order allows for/foldr to both consume and construct infinite sequences, so long as it is at least sometimes lazy in its accumulators.
See also for/stream for a more convenient (albeit less flexible) way to lazily transform infinite sequences. (Internally, for/stream is defined in terms of for/foldr.)
Examples:
The suspension introduced by the #:delay option does not ordinarily affect the loop’s eventual return value, but if #:delay and#:result are combined, the accum-ids will be delayed in the scope of the result-expr in the same way they are delayed within the loop body. This can be used to introduce an additional layer of suspension around the evaluation of the entire loop, if desired.
Examples:
> (define evaluated-yet? #f) > (for/foldr ([acc (set! evaluated-yet? #t)] #:delay) () (force acc)) > evaluated-yet? #t
> (define evaluated-yet? #f) > (define start (for/foldr ([acc (set! evaluated-yet? #t)] #:delay #:result acc) () (force acc))) > evaluated-yet? #f > (force start) > evaluated-yet? #t
If the #:delay-as option is provided, then delayed-id is bound to an additional promise that returns the values of all accum-ids at once. When multiple accum-ids are provided, forcing this promise can be slightly more efficient than forcing the promises bound to theaccum-ids individually.
If the #:delay-with option is provided, the given delayer-idis used to suspend nested iterations (instead of the default, delay). A form of the shape (delayer-id recur-expr) is constructed and placed in expression position, where recur-expr is an expression that, when evaluated, will perform the next iteration and return its result (or results). Sensible choices for delayer-id include lazy,delay/sync, delay/thread, or any of the other promise constructors from racket/promise, as well as thunk fromracket/function. However, beware that choices such asthunk or delay/name may evaluate their subexpression multiple times, which can lead to nonsensical results for sequences that have state, as the state will be shared between all evaluations of the recur-expr.
If multiple accum-ids are given, the #:delay-with option is provided, and delayer-id is not bound to one of delay,lazy, delay/strict, delay/sync,delay/thread, or delay/idle, the accum-ids will not be bound at all, even within the loop body. Instead, the #:delay-asoption must be specified to access the accumulator values viadelayed-id.
Added in version 7.3.0.3 of package base.
(for* (for-clause ...) body-or-break ... body)
Like for, but with an implicit #:when #t between each pair of for-clauses, so that all sequence iterations are nested.
Example:
(for*/list (for-clause ...) body-or-break ... body) (for*/lists (id ... maybe-result) (for-clause ...) body-or-break ... body) (for*/vector maybe-length (for-clause ...) body-or-break ... body) (for*/hash (for-clause ...) body-or-break ... body) (for*/hasheq (for-clause ...) body-or-break ... body) (for*/hasheqv (for-clause ...) body-or-break ... body) (for*/hashalw (for-clause ...) body-or-break ... body) (for*/and (for-clause ...) body-or-break ... body) (for*/or (for-clause ...) body-or-break ... body) (for*/sum (for-clause ...) body-or-break ... body) (for*/product (for-clause ...) body-or-break ... body) (for*/first (for-clause ...) body-or-break ... body) (for*/last (for-clause ...) body-or-break ... body) (for*/fold ([accum-id init-expr] ... maybe-result) (for-clause ...) body-or-break ... body) (for*/foldr ([accum-id init-expr] ... accum-option ...) (for-clause ...) body-or-break ... body)
Like for/list, etc., but with the implicit nesting offor*.
Example:
> (for*/list ([i '(1 2)] [j "ab"]) (list i j)) '((1 #\a) (1 #\b) (2 #\a) (2 #\b))
Changed in version 7.3.0.3 of package base: Added the for*/foldr form.
Changed in version 8.5.0.3: Added the for*/hashalw form.
3.18.2 Deriving New Iteration Forms🔗ℹ
(for/fold/derived orig-datum ([accum-id init-expr] ... maybe-result) (for-clause ...) body-or-break ... body)
Like for/fold, but the extra orig-datum is used as the source for all syntax errors.
A macro that expands to for/fold/derived should typically usesplit-for-body to handle the possibility of macros and other definitions mixed with keywords like #:break.
Examples:
; If we misuse for/digits, we can get good error reporting ; because the use of orig-datum allows for source correlation: eval:4:0: for/digits: bad sequence binding clause at: a in: (for/digits (a (in-list (quote (1 2 3)))) (b (in-list (quote (4 5 6)))) (+ a b)) 963 ; Another example: compute the max during iteration: > (define-syntax-parse-rule (for/max clauses body ... tail-expr) #:with original this-syntax #:with ((pre-body ...) (post-body ...)) (split-for-body this-syntax #'(body ... tail-expr)) (for/fold/derived original ([current-max -inf.0]) clauses pre-body ... (define maybe-new-max (let () post-body ...)) (if (> maybe-new-max current-max) maybe-new-max current-max))) > (for/max ([n '(3.14159 2.71828 1.61803)] [s '(-1 1 1)]) (* n s)) 2.71828
Changed in version 6.11.0.1 of package base: Added the #:result form.
(for*/fold/derived orig-datum ([accum-id init-expr] ... maybe-result) (for-clause ...) body-or-break ... body)
Like for*/fold, but the extra orig-datum is used as the source for all syntax errors.
Examples:
eval:10:0: for*/digits: bad sequence binding clause at: ds in: (for*/digits (ds (in-list (quote ((8 3) (1 1))))) (d (in-list ds)) d) 1138
Changed in version 6.11.0.1 of package base: Added the #:result form.
(for/foldr/derived orig-datum ([accum-id init-expr] ... accum-option ...) (for-clause ...) body-or-break ... body) (for*/foldr/derived orig-datum ([accum-id init-expr] ... accum-option ...) (for-clause ...) body-or-break ... body)
Like for/foldr and for*/foldr, but the extraorig-datum is used as the source for all syntax errors as infor/fold/derived and for*/fold/derived.
Added in version 7.3.0.3 of package base.
(define-sequence-syntax id expr-transform-expr clause-transform-expr) clause-transform-expr : (syntax? . -> . syntax?)
Defines id as syntax. An (id . rest) form is treated specially when used to generate a sequence in afor-clause of for (or one of its variants). In that case, the procedure result of clause-transform-expr is called to transform the clause.
When id is used in any other expression position, the result of expr-transform-expr is used. If it is a procedure of zero arguments, then the result must be an identifier other-id, and any use of id is converted to a use ofother-id. Otherwise, expr-transform-expr must produce a procedure (of one argument) that is used as a macro transformer.
When the clause-transform-expr transformer is used, it is given a for-clause as an argument, where the clause’s form is normalized so that the left-hand side is a parenthesized sequence of identifiers. The right-hand side is of the form (id . rest). The result can be either #f, to indicate that the forms should not be treated specially (perhaps because the number of bound identifiers is inconsistent with the (id . rest) form), or a new for-clause to replace the given one. The new clause might use :do-in. To protect identifiers in the result ofclause-transform-expr, use for-clause-syntax-protectinstead of syntax-protect.
Examples:
(:do-in ([(outer-id ...) outer-expr] ...) outer-defn-or-expr ([loop-id loop-expr] ...) pos-guard ([(inner-id ...) inner-expr] ...) maybe-inner-defn-or-expr pre-guard post-guard (loop-arg ...)) maybe-inner-defn/expr = | inner-defn-or-expr
A form that can only be used as a seq-expr in afor-clause of for (or one of its variants).
Within a for, the pieces of the :do-in form are spliced into the iteration essentially as follows:
(let-values ([(outer-id ...) outer-expr] ...) outer-defn-or-expr (let loop ([loop-id loop-expr] ...) (if pos-guard (let-values ([(inner-id ...) inner-expr] ...) inner-defn-or-expr (if pre-guard (let body-bindings (if post-guard (loop loop-arg ...) done-expr)) done-expr)) done-expr)))
where body-bindings and done-expr are from the context of the :do-in use. The identifiers bound by thefor clause are typically part of the ([(inner-id ...) inner-expr] ...) section. When inner-defn-or-expr is not provided (begin) is used in its place.
Beware that body-bindings and done-expr can contain arbitrary expressions, potentially including set! onouter-id or inner-id identifiers if they are visible in the original for form, so beware of depending on such identifiers in post-guard and loop-arg.
The actual loop binding and call has additional loop arguments to support iterations in parallel with the :do-inform, and the other pieces are similarly accompanied by pieces from parallel iterations.
For an example of :do-in, see define-sequence-syntax.
Changed in version 8.10.0.3 of package base: Added support for non-emptymaybe-inner-defn-or-expr.
Changed in version 8.2.0.4 of package base: Changed to just return stx instead of returning “armed” syntax.
Binds id for reference via a #:splice clause in afor form. The proc-expr expression is evaluated inphase level 1, and it must produce a procedure that accepts a syntax object and returns a syntax object.
The procedure’s input is a syntax object that appears after#:splice. The result syntax object must be a parenthesized sequence of forms, and the forms are spliced in place of the#:splice clause in the enclosing for form.
Examples:
'(0 0)'(0 1)'(0 2)'(1 0)'(1 1)'(1 2)'(2 0)'(2 1)'(2 2)
Added in version 8.4.0.3 of package base.
3.18.3 Iteration Expansion🔗ℹ
The bindings documented in this section are provided by the racket/for-clause library, not racket/base or racket.
Added in version 8.11.1.4 of package base.
3.18.4 Do Loops🔗ℹ
(do ([id init-expr step-expr-maybe] ...) (stop?-expr finish-expr ...) expr ...) step-expr-maybe = | step-expr
Iteratively evaluates the exprs for as long asstop?-expr returns #f.
To initialize the loop, the init-exprs are evaluated in order and bound to the corresponding ids. The ids are bound in all expressions within the form other than theinit-exprs.
After the ids have been bound, the stop?-expr is evaluated. If it produces #f, each expr is evaluated for its side-effect. The ids are then effectively updated with the values of the step-exprs, where the defaultstep-expr for id is just id; more precisely, iteration continues with fresh locations for theids that are initialized with the values of the correspondingstep-exprs.
When stop?-expr produces a true value, then thefinish-exprs are evaluated in order, and the last one is evaluated in tail position to produce the overall value for thedo form. If no finish-expr is provided, the value of the do form is #.