#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))) ] @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))) ]