We have all had the thought. Why oh why are not all our problems solved by a nice recursive algorithm over a list?. We see a neat problem. We smile and think about all the nice things we shall do right after finishing off this little tidbit, this delicious little bite of a problem. Then we scrape the surface. Under there is a jungle of state. Suddenly our little recursive list-mangling is not enough. To defeat the anxiety rising within us, we quickly write something using map, filter, zip and reduce. We test it. We smile again. We test it on real world data. The smile stiffens. The little bite has turned in to an o(n²) turd.
We raise our gaze and see the disgustingly mutating little wonders we could have achieved using python list comprehensions. We look sideways and see the magnificent, fantastic, but abominable loop macro in Common Lisp. Even they, the elitist ultra-productive scumbags, hate it, yet they sieze every moment to make underhanded comments about named lets. Why, oh why is the world not as elegant as we want it to be?
We think again about how python programmers write scheme, and shudder. No! Never that!, we think to ourselves. A vague idea starts forming. A quote surfaces from the depths of our minds “Above all the wonders of Lisp’s pantheon stand its metalinguistic tools; by their grace have Lisp’s acolytes been liberated from the rigid ascetism of lesser faiths”. Maybe we should try to do it ourselves. Thinking about it, we have actually written some macros before. Anaphoric ifs, some boilerplate removal macros, and oh! Even an almost-working implementation of SRFI-26. It passed nearly all the tests, and we couldn’t be bothered to go any further. But a looping facility… How hard can it be?
– Linus Björnstam, September 2020
Goof-loop is like the name suggests a looping facility. It is based in many ways on things I learned porting Racket’s for-loops to guile and on the marvel that is (chibi loop) in chibi-scheme. The goals of goof-loop are:
The only other lisp looping facility that I know of that provides these things is Common Lisps iterate package. Iterate does however do a lot of things that are cumbersome to do in portable scheme, and would be prohibitively complicated to implement efficiently in a portable way. Unless, of course, one considers CPS-conversion of arbitrary scheme code using syntax-rules simple
On a side note: if anyone has the source of Olin Shivers’ loop package described in his paper “Anatomy of a loop”, please send me an email.
So, how does it look? A slightly contrived example, a naive sieve of Erathostenes:
(define (erathostenes n) (define vec (make-vector n #t)) (loop/list ((:for i (up-from 2 (:to n))) (:when (vector-ref vec i))) ;; Here we set all multiples of i to #f (loop ((:for j (up-from (* 2 i) (:to n) (:by i))) (vector-set! vec j #f)) i))
Calling (erathostenes 10)
returns a list of all primes below 10.
The example above can also be written using “subloops”:
(define (erathostenes n) (define vec (make-vector n #t)) (loop ((:for i (up-from 2 (:to n))) (:when (vector-ref vec i)) (:acc lst (listing i)) (:for j (up-from (* 2 i) (:to n) (:by i)))) (vector-set! vec j #f)))
The loop grammar is the following:
(loop [name] (loop-clause ...) [=> final-expr] body ...) name = identifier loop-clause = (:for id id* ... seq-expr) | (:acc id id* ... seq-expr) | (:bind (id id* ... expr) ...) | (:when guard-expr) | (:unless guard-expr) | (:break break-expr) | (:final guard-expr) | (:do expr ...) | :subloop seq-expr = a macro that conforms to the looping protocol described below. final-expr = The expression executed at loop finalization. If none is given, one returning all :acc values is generated body = A sequence of expressions executed after the loop clauses mostly useful to control in which fashion subsequent loops execute.
If a name
is provided, it will be bound to a macro that allows named update of loop variables:
(loop lp ((:for a (in 0 (+ a 1))) (:break (> a 9))) => '() (if (= 4 a) (cons a (lp (=> a 8))) (cons a (lp))))
This rather inane example would return (0 1 2 3 4 8 9)
. Read more about this in the part about loop variables.
A subloop is a distinction between an outer loop and an inner loop. A subloop means that: for each element yielded by an outer loop, the inner loop is run until exhaustion. If a :for clause is placed after any non-:for clause, it is considered a subloop.
(loop ((:for a (in-list '(1 2 3))) :subloop (:for b (up-from 0 (:to a))) (:acc acc (listing (cons a b))))) ;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
The above :subloop
clause is equivalent to :when #t
and :unless #f
. A :break
clause will immediately stop execution of the loop:
(loop ((:for a (in-list '(1 2 3))) (:break (= 3 a)) (:for b (up-from 0 (:to a))) (:acc acc (listing (cons a b))))) ;; => ((1 . 0) (2 . 0) (2 . 1))
And a :final guard will let the subsequent subloops execute once.
(loop ((:for a (in-list '(1 2 3 4))) (:final (= 3 a)) (:for b (up-from 0 (:to a))) (:acc acc (listing (cons a b))))) ;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
The :final clause is actually equivalent to something alike the following:
(loop ((:for a (in-list '(1 2 3 4))) ;; this works because :acc bindings are promoted "outwards". (:break final) (:acc final (in-value (initial #f) #t (if (= 3 a)))) (:acc acc (listing (cons a b)))))
This means that any clause above the final binding will be executed an extra time before the loop exits:
(loop ((:for a (up-from 1 4)) (:acc lst (listing a)) (:final (= a 2)) (:acc lst2 (listing a)))) ;; => (1 2 3) (1 2)
Both accumulating clauses and :for clauses have something called loop variables. In the case of (:for elt (in-list lst))
the loop variable would be the current pair where elt
is the car. Some :acc- or :for-clauses may expose their loop variables so that they can be queried or even updated.
In the case of the menioned in-list
we can choo se to expose the name of the current pair, as in the following example:
(define (interpose lst between) (loop lp ((:for elt pair (in-list lst))) => '() ;; reached only if lst is empty (if (null? (cdr pair)) (list elt) (cons* elt between (lp))))) (interpose '(1 2 3 4) ':) ; => (1 : 2 : 3 : 4)
In the above example we chose to bind the loop variable of in-list
to pair
. Using pair
we can then query if the next iteration is the empty list and decide not to interpose any value.
Some loop clauses have the option of exposing their loop variable(s). The option to do so is documented under the documentation for each clause.
The pure loop
macro is quite a big hammer for most tasks. Often we want to do simple things, like collect elements into a list or a vector, which means the extra housekeeping of separating accumulators and for clauses are too much heavy lifting. goof-loop provides several simpler forms that can be used in those cases. In these simpler forms :acc is disallowed, and everything not identified as anything else is assumed to be a :for clause. The loop below accumulates the 100 first fibonacci numbers into a list.
(loop/list ((count (up-from 0 (:to 100))) (a (in 0 b)) (b (in 1 (+ a b)))) b)
The simple forms provided by goof-loop are the following:
(loop/first [:default #f] (clauses ...) body ...)
If any body is ever evaluated, stop and return the value of that evaluation. If no body is evaluated it returns the value specified by :default
, which defaults to #f.
(loop/last [:default #f] (clauses ...) body ...)
Returns the result of the last body to be evaluated. If no body is evaluated it returns the value specified by :default
, which defaults to #f.
(loop/list (clauses ...) body ...)
Iterates over clauses
and builds a list of the result of every evaluation of body. The order of the list is the same as the order body was evaluated in. The result of the first evaluation of body is the first element of the resulting list.
The list returned is the same even when used with multi-shot continuations.
If no body is evaluated, the result is the empty list.
(loop/product (clauses ...) body ...)
Multiplies each evaluated body. If no body is evaluated, 1 is returned
(loop/sum (clauses ...) body ...)
Sums the result of each evaluated body. If no body is evaluated, 0 is returned.
(loop/and (clauses ...) body ...)
If all evaluated bodies return truthy, return the result of the last evaluated body. If any body returns #f, stop iteration and return #f. If no body is evaluated, #t is returned.
(loop/or (clauses ...) body ...)
If any evaluated body returns truthy, stop iteration and return the result of that body. If no body returns truthy, return #f. If no body is evaluated, return #f.
(loop/list/parallel (clauses ...) body ...)
Like loop/list, but evaluates each body in parallel.
A :for clause specifies a number of identifiers that are bound to a value from a sequence. (:for i (up-from 0 10)))
will bind a to all integers between 0 and 10 (excluseve) in order. The first time the loop is executed, it is bound to 0, then 1, then 2, and so on. Some loops has support for exposing their loop variables to the user. In the case of (:for elt pair (in-list lst))
, elt
will be bound to the car of pair
, whereas pair
will be bound to the loop variable used by in-list
to hold the current position of the list.
(:for binding (in start [update [stop]]))
Binds a loop variable to binding
. It’s first value is start
. It is updated by the update
expression, or is left unchanged if no such expression is present. If a stop
expression is provided, it will be evaluated before each loop body. If the stop
expression returns true, the iteration will be considered exhausted.
(loop ((:for a (in 0 b)) (:for b (in 1 (+ a b) (> b 20)))) (display b) (newline))
Will print all fibonacci numbers below 20.
binding
is a loop variable and can thus be changed using named updates.
(:for binding (up-from start [(:to bound)] [(:by step)]))
(:for binding (up-from start [bound [by]]))
Binds binding
to the number start
up to bound
(exclusive!) by step
. If no bound
is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteration if a step
other than 1
is wanted.
binding
is a loop variable and can thus be changed using named updates.
(:for binding (down-from start [(:to bound)] [(:by step)])
(:for binding (down-from start [bound [by]]))
Binds binding
to the number (- start 1)
down to bound
(inclusive!) by step
. If no bound
is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteration if a step
other than 1
is wanted.
binding
is a loop variable and can thus be changed using named updates.
(:for binding [pair] (in-list expr [by])
Binds binding
to the car of the loop variable pair
. pair
is advanced by applying the procedure by
to it (defaulting to cdr
). The iteration stops when pair
is the empty list.
(:for binding [pairs] (in-lists expr [by])
Works the same as in-list
, but expr
must evaluate to a list of lists. binding
is bound to the car of those lists, and they are advanced by by
, defaulting to cdr
.
(:for binding [index] (in-vector expr [low [high]]))
Binds binding
to all elements in the vector produced by expr
in order from low
to high
. low
defaults to 0 and high
defaults to the last index of the vector.
The vector produced by expr
is bound outside the loop body, and index
is the loop variable.
(:for binding [index] (in-reverse-vector expr [high [low]]))
Binds binding
to all elements in the vector produced by expr
in reverse order from high
to low
. high
defaults to the last element of the vector and low
defaults to 0.
The vector produced by expr
is bound outside the loop body, and index
is the loop variable.
(:for binding [index] (in-string expr [low [high]]))
Binds binding
to all elements in the string produced by expr
in order from low
to high
. low
defaults to 0 and high
defaults to the last index of the string.
The string produced by expr
is bound outside the loop body, and index
is the loop variable.
(:for binding [index] (in-reverse-string expr [high [low]]))
Binds binding
to all elements in the vector produced by expr
in reverse order from high
to low
. high
defaults to the last element of the vector and low
defaults to 0.
The string produced by expr
is bound outside the loop body, and index
is the loop variable.
(:for binding (in-port port [reader [eof?]]))
Binds binding
to the result of calling reader
on port
. Iteration stops when (eof? binding)
returns true.
(:for binding (in-file path [reader [eof?]]))
Opens the file located at path
(which is a string) and binds binding
to the result of calling reader
on the opened port. Iteration stops when (eof? binding)
returns true.
(:for binding (in-generator gen))
Binds binding to the result of calling the SRFI-158-compatible generator gen
. Iteration stops when gen
returns the end-of-file object.
(:for binding (in-hash hash))
Binds binding
to the (key . value)
pairs of the hash-table hash
. May, as all body-binding variables, be pattern-matched:
(loop/list (((_ . val) (in-hash hash-table))) val)
(:for binding (in-cycle iterator))
Binds the values produced by iterator
to binding
. Once iterator
stops, it starts over.
(:for binding (in-indexed iterator))
Binds binding
to (n . value)
, where n
is the nth value produced by iterator
and value
is that value. n
starts from 0
(:for binding (stop-before iterator pred))
Binds binding
to the values produced by iterator
until pred
applied to that value returns true. The iterator is then considered exhausted. Useful in subloops where one might want to end internal iteration without :break-ing.
(:for binding (stop-after iterator pred))
Binds binding
to the values produced by iterator
until pred
applied to that value returns true. It then produces that last value. The iterator is then considered exhausted. Useful in subloops where one might want to end internal iteration without :break-ing.
Accumulating clauses differ from :for-clauses in 2 significant ways. They have a final value available in the final-expr
, and they keep their state throughout the loop. In the case of a loop with one subloop, the :for-clauses reset their state every time the subloop is entered. :acc-clauses will always keep their state.
Another small thing is that for some :acc-clauses, the binding
may sometimes only be visible to the user in the final-expr
, but like :for-clauses they sometimes offer the programmer to name the loop variables.
Many accumulating clauses support an if
form. If such a clause is given, accumulation will only happen if the guard clause returns true.
(:acc binding (listing [(:initial init)] expr [if guard]))
Accumulates expr
into a list. ´bindingis only accesible in the final-expression. The list is in the same order as the loop bodies were evaluated. If
initialis given that will be used as the tail of the accumulated results. It defaults to
’()`.
(:acc binding (listing-reverse [(:initial init)] expr [if guard]))
The same as listing
but the resulting list in in reverse order. If the order of the resulting list does not matter, this will be faster than the regular listing as it will not preform any reverse at the end.
(:acc binding (appending [(:initial init)] expr [if guard]))
expr
evaluates to a list that is then appended to the accumulated result.
(loop ((:for elt (in-list '((1 2) (3 4)))) (:acc acc (appending (:initial '(0)) elt))) => acc) ;; => (0 1 2 3 4)
(:acc binding (appending-reverse [(:initial init)] expr [if guard]))
expr
evaluates to a list that is then consed element by element onto the already accumulated results. The default initial value is '()
.
(loop ((:for elt (in-list '((1 2) (3 4)))) (:acc acc (appending-reverse (:initial '(0)) elt))) => acc) ;; => (4 3 2 1 0)
(:acc binding (summing [(:initial init)] expr [(if guard)]))
Adds the result of expr
together using +
. The default initial value is 0.
(:acc binding (multiplying [(:initial init)] expr [(if guard)]))
Multiplies the result of expr
using *
. The default initial value is 1.
(:acc binding (hashing [(:initial init)] key value [(if guard)]))
Adds the mapping (key => value)
to the hashtable binding
using equal?-hashing. The initial hash table is an empty hash-table. binding
is bound to the hash table throughout the loop, and its content can be mutated in the loop body.
(:acc binding (hashving [(:initial init)] key value [(if guard)]))
Adds the mapping (key => value)
to the hashtable binding
using eqv?-hashing. The initial hash table is an empty hash-table. binding
is bound to the hash table throughout the loop, and its content can be mutated in the loop body.
(:acc binding (hashqing [(:initial init)] key value [(if guard)]))
Adds the mapping (key => value)
to a hashtable using eq?-hashing. The initial hash table is an empty hash-table.binding
is bound to the hash table throughout the loop, and its can be mutated in the loop body.
(:acc binding [index] (vectoring expr [(:length len) [(:fill fill)]]))
Accumulates the result of expr
into a vector. If len
and fill
is given the vector will be at most len
elements long and any unfilled indexes will contain the element fill
. The loop will exit when len
elements have been accumulated.
If length
is not given, the vector will be expanded as required.
A vectoring clause adds an implicit (:break (= index len))
after the vectoring clause. Once the last element of the vector is filled, the loop will stop and no subsequent clauses or body will be executed.
binding
is bound to the vector throughout the loop, and its content mutated in the loop body.
goof-loop is extensible using regular syntax-rules macros. The protocol for both :acc- and :for-clauses is identical, except that the behaviour of the different parts are slightly different.
The following example defines the simple :for-driver in-alist
:
(define-syntax in-alist (syntax-rules () ((_ ((key val) (expr)) next . rest) (next ;; Outer let bindings. These are bound before the loop body ;; and are in the form ((id id* ... expr) ...). () ;; Loop variables. Here we bind %cursor to expr, which at each iteration is updated ;; by (cdr %cursor). In the form ((name init update) ...) ((%cursor expr (cdr %cursor))) ;; stop guards. In the form (stop-expr ...) ((null? %cursor)) ;; Body bindings. If we need to do anything to the loop variables before ;; any tests except the stop guards are run, this is where we do it. ;; In the form ((bindings ... expr) ...) where the bindings may be ;; (ice-9 match) patterns. (((key . val) (car %cursor))) ;; This last one is finalizers. They run on any kind of exit from the same ;; loop or any subloop where the :for-clause is in scope. () . rest)))) (register-loop-clause 'for #'in-alist)
In short, the clause (:for key value (in-alist alist-expr)) expands to:
(in-alist ((key val) (alist-expr)) next-macro . rest)
You should never have to care about rest
. That is the complete state of the expansion of loop, which we have to pass around since most of goof-loop is written in syntax-rules.
Going from the top we first have the outer let bindings. These are bound outside the loop, and are mostly used for binding things like vectors or ports that do not change during the loop.
The next one are loop variables. Here we provide three things: variable name, initial expression and update. The update expression is used to bind the variable to the next value of the sequence.
Stop guards are just that. In this case, when (null? %cursor)) returns true, the sequence is considered exhausted. If the loop is in a subloop, the current loop stops and the outer loop continues. If there is only one loop, it halts.
Body bindings are bound before anything except the stop guards are run. They may be an (ice-9 match) pattern, and are multiple-value aware. (a b (div-and-mod 10 7))
is valid, and so is ((key . val) (let ((c (car %cursor))) (values (car c) (cdr c)))).
Finalizers are run everytime the current loop or any subloop below the current loop exits. This can be used to close resources. (in-file ...)
uses this.
register-loop-clause registers the loop clause so that it can be verified to exist at expansion time.
The following code implements the accumulator (alisting …) which accumulates two values into an alist:
(define-syntax alisting (syntax-rules (:acc) ((_ :acc ((name) (key val)) next . rest) (next ;; Outer let bindings. These are bound before the loop body ;; and are in the form ((id id* ... expr) ...). () ;; Loop variables. Here we bind %cursor to expr, which at each iteration is updated ;; by (cdr %cursor). In the form ((name init update) ...) ((%cursor '() (cons (cons key val) %cursor))) ;; stop guards. Currently do nothing. () ;; Body bindings. Not used much. () ;; final-bindings. The same thing as wrapping the final-expr in ;; (let ((name (reverse %cursor))) ...) ((name (reverse %cursor))) . rest)))) (register-loop-clause 'acc #'alisting)
The first difference is that the first argument to an accumulator always is the symbol :acc.
So, number one is the same as for :for-clauses. Number two looks the same, except the name %cursor and it’s initial value is promoted to the outermost loops. This is because the value of the accumulator needs to be available in all stages of the loop to be available in the final-expr. The initial value cannot therefore reference any things bound in outer loops.
No loop currently use the stop guards, this is because the stop guards do not stop iteration completely, just end the current loop. This might change.
The body bindings can be used to bind variables to make information from the accumulator visible, but otherwise not used.
Final bindings. Binds whatever variable name you chose to whatever expression you chose in the final-expression.
register-loop-clause registers the alisting clause so that it can be verified to exist at expansion time.
The main chunk of a loop expands into something like the following: A goof loop expands into something looking like this:
(let* (OUTER-BINDING ... final-function (lambda (FINAL-BINDING) FINAL-EXPR)) (let goof-loop ((ACCUMULATOR ACCUMULATOR-INIT) ... (LOOP-VAR LOOP-VAR-INIT) ...) (if (or CHECK...) (begin FOR-CLAUSE-FINALIZER ... (final-function (ACCUMULATOR-FINALIZER ACCUMULATOR) ...)) (let ((BODY-BINDING ... BODY-BINDING-EXPR) ...) (match-let ((PARENTHESISED-PATTERN MATCH-EXPR)) (CLAUSES-EXPANSION LOOP-BODY ...)))))) ;; CLAUSES-EXPANSION: ;; ((:when test). rest) => (if test (begin . rest) (goof-loop ACCUMULATOR ... LOOP-VAR-NEXT ...)) ;; ((:unless test) . rest) ;; is the same as above, but the test is negated ;; ((:acc var (accumulate-expr bleh)) . rest) (let ((ACCUMULATOR ACCUMULATE) ...) (if (or ACCUMULATOR-TESTS ...) (begin FOR-CLAUSE-FINALIZER ... (final-function (ACCUMULATOR-FINALIZER ACCUMULATOR) ...)) (begin . rest))) ;; ((:break test) . rest) (if test (begin FOR-CLAUSE-FINALIZER ... (final-function (ACCUMULATOR-FINALIZER ACCUMULATOR) ...)) (begin . rest)) ;; ((:bind (USER-BINDING ... expr) ...) . rest) (let ((USER-BINDING ... expr) ...) (match-let ((PARENTHESISED-PATTERN MATCH-EXPR) ...) . rest)) ;; ((:do expr ...) . rest) (begin expr ...) . rest ;; ((:final test) . rest) ((:break final) (:acc final (special-final-accumulator (initial #f) #t (if test))) . rest)
OUTER-BINDING: are provided by accumulators or for clauses for bindings that are not passed as an argument to the loop, for example a vector. The vector is bound here, and the index into the vector is the thing iterated over.
FINAL-BINDING and FINAL-EXPR: When the iteration ends, this function is called with the results of the :acc clauses. In the case of (:acc lst-acc (listing …)), the name of the accumulator is never lst-acc in the loop body, but only in the FINAL-EXPR. In case of (listing …) the accumulated results are reversed before the final function.
ACCUMULATOR and LOOP-VAR: ACCUMULATOR holds the current state of an accumulator clause. This is not necessarily the same binding as the user provided as the name, as described above. LOOP-VAR is the current state of a :for clause.
CHECK: Checks for :for-clauses. In the case of (in-list …) this would check for (not (pair? …)).
FOR-CLAUSE-FINALIZER: some :for clauses need to be finalized. In the case of (in-file …) the open file handle is closed at any point where the iteration stops.
ACCUMULATOR-FINALIZER: ACCUMULATOR-FINALIZER is any preprocessing done to ACCUMULATOR before passing it on to the final-function. In the case of (listing …) that would be (reverse …).
BODY-BINDING and BODY-BINDING-EXPR: BODY-BINDING are the names the user provided for the body bindings. In the case of (:for a (in-list ’(1 2 3))) the body binding would be (a (car name-of-loop-variable)). The body binding may be an (ice-9 match) pattern. More on that below.
PARENTHESISED-PATTERN and MATCH-EXPR: If a USER-BINDING or BODY-BINDING is not an identifier, it is presumed to be a match-let pattern. The result is bound to a variable and matched against this match-let.
LOOP-BODY, ACCUMULATE, and LOOP-VAR-NEXT: The user supplied body of the loop. If the loop is not named (i.e: in loops where the user controls the iteration) an expression for the next loop iteration is added to the body. ACCUMULATE is the expression the accumulator clause provided to accumulate a new value. For (:acc acc (listing elem)) that is (cons elem acc). LOOP-VAR-NEXT is the expression evaluated to get the next iteration’s loop variable. In the case of (in-list lst) that is (cdr lst). If a loop name is provided there is no implicit next loop.
ACCUMULATOR-INIT and LOOP-VAR-INIT: ACCUMULATOR-INIT are ALL accumulator init values, including the ones in subloops. For (listing …) that is the empty list. LOOP-VAR-INIT is the initial loop vars.
USER-BINDING: an identifier or an (ice-9 match) pattern. If any of the supplied USER-BINDINGs are patterns, they are destructured in the subsequent match-let. goof uses let and let* from srfi-71, and as such is multiple-values-aware. You can do (:bind (one (fst . snd) (values 1 (cons 3 4))))
, and it will work as expected.
In case of subloops, those are placed instead of LOOP-BODY. They use the same final-function, and instead of quitting when any CHECK triggers they go out to the outer loop.
The bulk of goof-loop is written in portable syntax-rules. That code can be found in goof-impl.scm
and all files under the goof
directory. The major non-portable part is the macro that is bound in every loop to the user-given name of the loop. In the guile implementation this is implemented in syntax-case, and should be portable to any r6rs scheme. The guile implementation does a non-hygienic comparison of the variables in the named update, so to not have to deal with unwanted shadowing:
(loop lp ((:for a (up-from 0 10))) => '() (let ((a (+ a 1))) (if (odd? a) (cons a (lp (=> a (+ a 2)))) (lp)))) ;; => (1 5 9)
This is just a matter of comfort, since this kind of DWIM has no negative impact. I would regard an implementation without this as conforming as well.
The other part is loop/list/parallel. This is not required to actually be parallel, so defining it as an alias to loop/list is acceptable.
The third part is also a comfort macro; valid-clause?
. It simply verifies that the loop clause is bound and registered as a loop clasue. It makes it possible to get good error messages when a loop clause is misspelled or not imported. It can be portably defined as:
(define-syntax valid-clause? (syntax-rules () ((_ rest ... ) (rest ...))))
at the expense of error messages that become, for lack of a better word, ultra-shit. The register-loop-clause
form can be implemented as whatever NOP you prefer if you chose the above definition. If not, it takes two arguments, type (a symbol of either ’for or ’acc) and the appropriate object. In the guile version this is a syntax object, which is used because it allows the user to import the clause under a different name.
If your scheme of choice lacks these kinds of macrobatics, a simple symbol list helps a little bit, and is regarded as conformant.