The Common Lisp LOOP Macro for Racket
1 A Few of the Things the LOOP Can Do
2 Comparison with Racket’s for loops
6.1

The Common Lisp LOOP Macro for Racket

(See also: http://www.gigamonkeys.com/book/loop-for-black-belts.html)

    1 A Few of the Things the LOOP Can Do

    2 Comparison with Racket’s for loops

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"))