7.8

 (require loop) package: loop

This library provides the loop syntax, a drop-in replacement of named let. Unlike named let, the loop syntax has an option that will allow unchanged variables to be left out in function calls, as they will be carried to the next loop automatically. It also supports customized default values.

The code of this library can be found at https://github.com/sorawee/loop.

### 1Examples

Let’s suppose that you want to calculate the sum of even numbers up to 42. One dumb way is to use named let to iterate from 42 to 1 and add even numbers together.

 > (let go ([n 42] [sum 0]) (cond [(= n 0) sum] [(even? n) (go (sub1 n) (+ sum n))] [else (go (sub1 n) sum)]))

462

Notice that the variable sum is left unchanged in the else branch. With the loop syntax, we can write the following instead:

 > (loop go ([n 42] [sum 0 #:inherit]) (cond [(= n 0) sum] [(even? n) (go (sub1 n) #:sum (+ sum n))] [else (go (sub1 n))]))

462

Admittedly, this is not a perfect example because there are only few variables, so it doesn’t look really worth converting from the named let to the loop version. However, as the number of variables increases, the loop version will have an edge on. The loop version is also more scalable: adding more variables doesn’t require changing every recursive call.

As another example, let’s suppose we want to sum every number in a list that is after an even number. One dumb way using named let is as follows:

 > (let go ([xs '(4 4 7 3 2 5 8 5)] [sum 0] [proceed? #f]) (match xs ['() sum] [(list (? even? x) xs ...) (go xs (if proceed? (+ sum x) sum) #t)] [(list x xs ...) (go xs (if proceed? (+ sum x) sum) #f)]))

21

Notice that by default, proceed? is #f, and the variable is flipped to #t only when x is even. With the loop construct, we can write the following instead:

 > (loop go ([xs '(4 4 7 3 2 5 8 5)] [sum 0] [proceed? #f #:default]) (match xs ['() sum] [(list (? even? x) xs ...) (go xs (if proceed? (+ sum x) sum) #:proceed? #t)] [(list x xs ...) (go xs (if proceed? (+ sum x) sum))]))

21

### 2The Loop

syntax

 (loop proc-id ([id-required/optional init-expr extra-options] ...) body ...+)

extra-option =
| #:inherit
| #:default
| #:default default-expr
Evaluates the init-exprs; the resulting values become arguments in an application of a procedure (lambda (id-required ... #:id-optional [id-optional default-val] ...) body ...+), where proc-id is bound within the bodys to the procedure itself. For id-optional, default-val is either:

• The value of id-optional in the previous loop, if #:inherit is specified.

• The result of the evaluation of init-expr, if #:default is specified, but default-expr is not.

• The result of the evaluation of default-expr, if both #:default and default-expr are specified.

### 3Performance

Keyword arguments are incredibly very expensive. It could, for instance, make a program slower by 5x. Thankfully, in usual circumstances, Racket will be able to perform function application inlining, which completely restores the performance back to the original level.

It is still possible to circumvent Racket from performing inlining, however. In this case, the performance degradation will be noticable.

> (define N 10000000)
; Original performant code
 > (time (let go ([n N] [sum 0]) (cond [(= 0 n) sum] [(= 0 (remainder n 2)) (go (sub1 n) (+ sum n))] [(= 1 (remainder n 2)) (go (sub1 n) sum)])))

cpu time: 1305 real time: 1307 gc time: 0

25000005000000

; Equally performant code with loop syntax
 > (time (loop go ([n N] [sum 0 #:inherit]) (cond [(= 0 n) sum] [(= 0 (remainder n 2)) (go (sub1 n) #:sum (+ sum n))] [(= 1 (remainder n 2)) (go (sub1 n))])))

cpu time: 1367 real time: 1370 gc time: 0

25000005000000

; Non-performant code with loop syntax
; due to the inability to perform inlining
 > (time (loop go ([n N] [sum 0 #:inherit]) (let ([go go]) (cond [(= 0 n) sum] [(= 0 (remainder n 2)) (go (sub1 n) #:sum (+ sum n))] [(= 1 (remainder n 2)) (go (sub1 n))]))))

cpu time: 5456 real time: 5461 gc time: 147

25000005000000