The Common Lisp LOOP Macro for Racket
(See also: http://www.gigamonkeys.com/book/loop-for-black-belts.html)
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 for loop, except it’s more powerful than either.
1 A Few of the Things the LOOP Can Do
It can implement simple while and until loops like those seen in other programming languages, while also having its own local variables:
> (require "main.rkt")
> (loop with x = 0 while (< x 3) do (displayln x) (set! x (add1 x)))
0
1
2
...and it can implement the classic BASIC FOR loop
> (require "main.rkt")
> (loop for line from 10 to 40 by 10 do (displayln (format "~a PRINT \"LOOK AROUND YOU! \";" line)))
10 PRINT "LOOK AROUND YOU! ";
20 PRINT "LOOK AROUND YOU! ";
30 PRINT "LOOK AROUND YOU! ";
40 PRINT "LOOK AROUND YOU! ";
...and it can do exactly the same thing a number of times:
> (require "main.rkt")
> (loop repeat 2 do (displayln 'Deja-Vu!))
Deja-Vu!
Deja-Vu!
But it can also iterate over lists while simutaneously incrementing counters and filtering on either one, returning a list.
> (require "main.rkt")
> (loop for x in '(a b c d e f g) for y from 0 when (even? y) collect x) '(a c e g)
Though the syntax resembles that of for/list, LOOP’s when clause does not cause a nested loop to begin. In fact, additional for clauses are illegal after the when clause.
It can iterate over multiple lists simultaneously and collect the results in a hash table:
> (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)) '#hash((g . 7) (f . 6) (c . 3) (b . 2) (a . 1) (e . 5) (d . 4))
(The supported collection types are list (default), vector, string, bytes, hash, and hash/immutable.)
Using the across iteration keyword, it can iterate over strings, vectors, and bytes:
> (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))
'#hash(("number-5" . 286)
("number-7" . 258)
("char-6" . #\f)
("char-3" . #\c)
("byte-7" . 103)
("number-1" . 493)
("byte-3" . 99)
("char-7" . #\λ)
("number-2" . 286)
("byte-5" . 101)
("char-5" . #\e)
("number-4" . 283)
("byte-1" . 97)
("number-3" . 81)
("byte-4" . 100)
("char-4" . #\d)
("char-2" . #\b)
("byte-2" . 98)
("number-6" . 290)
("char-1" . #\a)
("byte-6" . 102))
It can filter into multiple output collections on differing criteria (you can only change the collection type of unnamed collections, though):
> (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))
'(a b c d e f g)
'(3 1 9 #\j #\z #\o 38 (some random list))
It can iterate over generators using the over iteration keyword:
> (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))
2
4
16
256
65536
4294967296
18446744073709551616
340282366920938463463374607431768211456
115792089237316195423570985008687907853269984665640564039457584007913129639936
13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096
179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216
...and hash tables
> (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) '(badfile.rkt)
It can count:
> (require "main.rkt")
> (require math/number-theory)
> (loop for n from 0 to 1000 for fib = (fibonacci n) count (even? fib)) 334
...more than one thing at a time, while simultaneously finding a minimum or maximum number:
> (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))))
'((square numbers = 4)
(prime numbers = 21)
(largest square = 144)
(even numbers = 334))
...and it can exit a multi-level-deep loop:
> (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))) ''17
It can bind into the structure of the elements in a list. Also, the conditional clauses can be nested:
> (require "main.rkt")
> (define test (loop for x from 1 to 10 for y downfrom 10 to 1 collect (cons x y)))
> test
'((1 . 10)
(2 . 9)
(3 . 8)
(4 . 7)
(5 . 6)
(6 . 5)
(7 . 4)
(8 . 3)
(9 . 2)
(10 . 1))
> (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)
'(x-too-small
x-too-small
x-too-small
x-too-small
x-too-small
(odd 5)
4
(odd 3)
2
(odd 1))
2 Comparison with Racket’s for loops
> (require "main.rkt")
> (for ([i '(1 2 3)] [j "abc"] #:when (odd? i) [k #(#t #f)]) (displayln (list i j k)))
(1 a #t)
(1 a #f)
(3 c #t)
(3 c #f)
> (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))))
(1 a #t)
(1 a #f)
(3 c #t)
(3 c #f)
> (require "main.rkt")
> (for ([(i j) #hash(("a" . 1) ("b" . 20))]) (display (list i j))) (a 1)(b 20)
> (loop for (i j) being each hash-pair in #hash(("a" . 1) ("b" . 20)) do (display (list i j))) (a 1)(b 20)
> (for ([i '(1 2 3)] [j "abc"] #:break (not (odd? i)) [k #(#t #f)]) (display (list i j k))) (1 a #t)(1 a #f)
> (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)))) (1 a #t)(1 a #f)
> (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))
> (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))) '((1 #\a #t) (1 #\a #f) (3 #\c #t) (3 #\c #f))
> (for/vector ([i '(1 2 3)]) (number->string i)) '#("1" "2" "3")
> (loop for i in '(1 2 3) with-collection-type vector collect (number->string i)) '#("1" "2" "3")
> (for/vector #:length 2 ([i '(1 2 3)]) (number->string i)) '#("1" "2")
> (loop for i in '(1 2 3) repeat 2 with-collection-type vector collect (number->string i)) '#("1" "2")
> (for/hash ((i '(1 2 3))) (values i (number->string i))) '#hash((1 . "1") (2 . "2") (3 . "3"))
> (loop for i in '(1 2 3) with-collection-type hash collect (cons i (number->string i))) '#hash((3 . "3") (2 . "2") (1 . "1"))