#lang scribble/manual @(require scribble/eval racket racket/sandbox (for-label racket)) @title{The Common Lisp LOOP Macro for Racket} (See also: http://www.gigamonkeys.com/book/loop-for-black-belts.html) @table-of-contents[] This is an implementation of Common Lisp's LOOP macro for Racket. The LOOP macro is similar to all of Racket's for/* macros, combined with Python's @scheme[for] loop, except it's more powerful than either. @section{A Few of the Things the LOOP Can Do} It can implement simple @italic{while} and @italic{until} loops like those seen in other programming languages, while also having its own local variables: @interaction[ (require "main.rkt") (loop with x = 0 while (< x 3) do (displayln x) (set! x (add1 x))) ] ...and it can implement the classic BASIC FOR loop @interaction[ (require "main.rkt") (loop for line from 10 to 40 by 10 do (displayln (format "~a PRINT \"LOOK AROUND YOU! \";" line))) ] ...and it can do exactly the same thing a number of times: @interaction[ (require "main.rkt") (loop repeat 2 do (displayln 'Deja-Vu!)) ] But it can also iterate over lists while simutaneously incrementing counters and filtering on either one, returning a list. @interaction[ (require "main.rkt") (loop for x in '(a b c d e f g) for y from 0 when (even? y) collect x) ] Though the syntax resembles that of @racket[for/list], LOOP's @racket[when] clause does not cause a nested loop to begin. In fact, additional @racket[for] clauses are illegal after the @racket[when] clause. It can iterate over multiple lists simultaneously and collect the results in a hash table: @interaction[ (require "main.rkt") (loop for x in '(a b c d e f g) for y in '(1 2 3 4 5 6 7) with-collection-type hash collect (cons x y)) ] (The supported collection types are @scheme[list] (default), @scheme[vector], @scheme[string], @scheme[bytes], @scheme[hash], and @scheme[hash/immutable].) Using the @scheme[across] iteration keyword, it can iterate over strings, vectors, and bytes: @interaction[ (require "main.rkt") (loop for char across "abcdefλ" for byte across #"abcdefg" for number across (loop repeat 7 collect (random 512) with-collection-type vector) for counter from 1 with-collection-type hash/immutable collect (cons (format "char-~a" counter) char) collect (cons (format "byte-~a" counter) byte) collect (cons (format "number-~a" counter) number)) ] It can filter into multiple output collections on differing criteria (you can only change the collection type of unnamed collections, though): @interaction[ (require "main.rkt") (define (sift pred? list) (loop for value in list when (pred? value) consing value into gold else consing value into dirt finally (return (values (reverse gold) (reverse dirt))))) (sift symbol? '(a b c 3 1 9 #\j #\z d #\o 38 e (some random list) f g)) ] It can iterate over generators using the @scheme[over] iteration keyword: @interaction[ (require "main.rkt") (require racket/generator) (define gen (generator () (loop for v = 2 then (expt v 2) do (yield v)))) (loop for bignum over gen repeat 11 do (displayln bignum)) ] ...and hash tables @interaction[ (require "main.rkt") (loop for (file test-status) being the hash-pairs in '#hash((file1.rkt . pass) (badfile.rkt . fail) (goodfile.rkt . pass)) if (eq? test-status 'fail) collect file) ] It can count: @interaction[ (require "main.rkt") (require math/number-theory) (loop for n from 0 to 1000 for fib = (fibonacci n) count (even? fib)) ] ...more than one thing at a time, while simultaneously finding a minimum or maximum number: @interaction[ (require "main.rkt") (require math/number-theory) (loop for n from 0 to 1000 for fib = (fibonacci n) count (even? fib) into evens count (prime? fib) into primes count (square-number? fib) into squares when (square-number? fib) maximize fib into biggest-square finally (return `((square numbers = ,squares) (prime numbers = ,primes) (largest square = ,biggest-square) (even numbers = ,evens)))) ] ...and it can exit a multi-level-deep loop: @interaction[ (require "main.rkt") (loop named outer for a-list in '((1 2 3 4 5 6 7 8 9) (10 11 12 13 14 15) (16 '17 18 19 20 21 22) (23 24 25 26 27 28 29)) do (loop for n in a-list unless (number? n) do (return-from outer n))) ] It can bind into the structure of the elements in a list. Also, the conditional clauses can be nested: @interaction[ (require "main.rkt") (define test (loop for x from 1 to 10 for y downfrom 10 to 1 collect (cons x y))) test (loop for (x . y) in test if (> x y) if (even? y) collect y else collect (list 'odd y) end else collect 'x-too-small) ] @section{Pattern matching} Pattern-matching on list values is supported in all assignments: @interaction[ (require "main.rkt") (apply string-append (loop for (token next-token) on '("separated" "by" "commas" "except" "at" "the" "end") collect token when next-token collect ",")) ] @interaction[ (require "main.rkt") (loop with (x y . z) = '(1 2 3 4 5) do (return (cons (+ x y) z))) ] The pattern matcher is permissive, being able to match a large number of variables to a short list: @interaction[ (require "main.rkt") (loop with (a b c d (e1 e2 e3) f g) = '(1 2 3) do (return (list a b c d (list e1 e2 e3) f g))) ] @section{Comparison with Racket's @scheme[for] loops} @interaction[ (require "main.rkt") (for ([i '(1 2 3)] [j "abc"] #:when (odd? i) [k #(#t #f)]) (displayln (list i j k))) (loop for i in '(1 2 3) for j across "abc" when (odd? i) do (loop for k across #(#t #f) do (displayln (list i j k)))) ] @interaction[ (require "main.rkt") (for ([(i j) #hash(("a" . 1) ("b" . 20))]) (display (list i j))) (loop for (i j) being each hash-pair in #hash(("a" . 1) ("b" . 20)) do (display (list i j))) (for ([i '(1 2 3)] [j "abc"] #:break (not (odd? i)) [k #(#t #f)]) (display (list i j k))) (loop for i in '(1 2 3) for j across "abc" while (odd? i) do (loop for k across #(#t #f) do (display (list i j k)))) (for/list ([i '(1 2 3)] [j "abc"] #:when (odd? i) [k #(#t #f)]) (list i j k)) (loop for i in '(1 2 3) for j across "abc" when (odd? i) append (loop for k across #(#t #f) collect (list i j k))) (for/vector ([i '(1 2 3)]) (number->string i)) (loop for i in '(1 2 3) with-collection-type vector collect (number->string i)) (for/vector #:length 2 ([i '(1 2 3)]) (number->string i)) (loop for i in '(1 2 3) repeat 2 with-collection-type vector collect (number->string i)) (for/hash ((i '(1 2 3))) (values i (number->string i))) (loop for i in '(1 2 3) with-collection-type hash collect (cons i (number->string i))) ] @section{The pathological case of @scheme[collect ... into]} Common Lisp allows you to do this: @racketblock[ (loop for x from 1 to 10 collect (* x 2) into doubled finally (return doubled)) ] Unfortunately, doing that without the loop taking exponential time requires mutating the final @scheme[cdr] of @scheme[doubled], which is not allowed in Racket. Therefore, this is implemented with @scheme[append], which has to cons up a brand new list every time, which will take progressively longer as @scheme[doubled] gets longer. A workaround is provided: @interaction[ (require "main.rkt") (loop for x from 1 to 10 cons (* x 2) into doubled finally (return (reverse doubled))) ] Furthermore, a warning is issued at compile time if you write or port code that uses the @scheme[collect ... into] clause: @interaction[ (require "main.rkt") (loop for x from 1 to 10 collect (* x 2) into doubled finally (return doubled)) ]