loop:   advanced named let
1 Examples
2 The Loop
loop
3 Performance
7.4

loop: advanced named let

Sorawee Porncharoenwase <sorawee.pwase@gmail.com>

 (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.

1 Examples

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

2 The 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:

3 Performance

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: 214 real time: 220 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: 234 real time: 234 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: 943 real time: 948 gc time: 25

25000005000000