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