Changed documentation to reflect recent changes
This commit is contained in:
parent
93134a1b21
commit
dd1589ab3a
3 changed files with 244 additions and 117 deletions
62
README.md
62
README.md
|
@ -41,6 +41,8 @@ It is written in a weird markdown/xml chimaera. You can find it in documentation
|
|||
|
||||
There is one caveat to this: some accumulating clauses (currently only vectoring with :length specified) have an implicit :break clause. This is tested AFTER the accumulation takes place. So: if the last position of the vector is set, the loop halts.
|
||||
|
||||
This of course also means accumulation is done before the body is executed, which is a bit counterintuitive.
|
||||
|
||||
``` scheme
|
||||
(loop ((:for a (in-list '(1 2 3)))
|
||||
(:acc vec (vectoring a (:length 2)))
|
||||
|
@ -163,12 +165,15 @@ The iterative fibonacci loop is weird to write using for/fold. goof fixes this:
|
|||
:subloop
|
||||
(:for b (up-from a (:to (+ a 2))))
|
||||
(:acc ab (listing b))))
|
||||
;; |> Entering subloop!
|
||||
;; |> Entering subloop!
|
||||
;; |> Entering subloop!
|
||||
;; => 6 (1 2 2 3 3 4)
|
||||
```
|
||||
|
||||
### Pattern matching
|
||||
|
||||
For clauses which bind "body bindings" (every one except (in ...)) can use pattern matching based on Alex Shinn's excellent match.scm.
|
||||
For clauses which bind "body bindings" (every one except (in ...), in-port, in-generator and in-file) can use pattern matching based on Alex Shinn's excellent match.scm.
|
||||
|
||||
``` scheme
|
||||
(loop ((:for (key . val) (in-list '((a . 1) (b . 2) c . 3)))
|
||||
|
@ -221,16 +226,32 @@ I also provide simplified forms for many common operations. Omitting :for is all
|
|||
Speed is good. Despite the rather involved expansion you can see in the documentation, due to inlining and dead-code elimination, the actual expansion shows some good code:
|
||||
|
||||
``` scheme
|
||||
,expand (loop ((:for a (in-list '(1 2 3 4)))
|
||||
(:when (even? a))
|
||||
(:acc acc (listing a))))
|
||||
$0 = (let* ((final-fun
|
||||
(lambda (acc) ((@@ (goof) values) acc)))
|
||||
(tmp-kons (@@ (goof) cons)))
|
||||
(let loop ((cursor-1 '()) (cursor '(1 2 3 4)))
|
||||
(if ((@@ (goof) not) ((@@ (goof) pair?) cursor))
|
||||
(final-fun ((@@ (goof) reverse) cursor-1))
|
||||
(let ((a ((@@ (goof) car) cursor))
|
||||
(succ ((@@ (goof) cdr) cursor)))
|
||||
(if (even? a)
|
||||
(let ((cursor (tmp-kons a cursor-1)))
|
||||
(if #f #f)
|
||||
(loop cursor succ))
|
||||
(loop cursor-1 succ))))))
|
||||
|
||||
;; This is mostly fluff that is removed using DCE, unrolling and inlining:
|
||||
> ,opt (loop ((:for a (in-list '(1 2 3 4)))
|
||||
(:when (even? a))
|
||||
(:acc acc (listing a))))
|
||||
$1 = (let loopy-loop ((cursor-1 '()) (cursor '(1 2 3 4)))
|
||||
(if (pair? cursor)
|
||||
(let ((a (car cursor)) (succ (cdr cursor)))
|
||||
(if (even? a)
|
||||
(loopy-loop (cons a cursor-1) succ)
|
||||
(loopy-loop cursor-1 succ)))
|
||||
(reverse cursor-1)))
|
||||
|
||||
$1 = (let* ((cursor (list 2)) (cursor (cons 4 cursor)))
|
||||
((@@ (goof) reverse) cursor))
|
||||
|
||||
;; well dang, the loop was optimized away almost completely...
|
||||
|
||||
;; loop/list, being less general, produces faster code that can be more easily optimized
|
||||
> ,opt (loop/list ((a (in-list '(1 2 3 4)))
|
||||
|
@ -244,7 +265,7 @@ $2 = (list 2 4)
|
|||
a)
|
||||
|
||||
;; This is actually the preferred way to do it in guile. Guile re-sizes the stack, so no stack overflows
|
||||
$5 = (let loopy-loop ((cursor (read)))
|
||||
$3 = (let loopy-loop ((cursor (read)))
|
||||
(if (pair? cursor)
|
||||
(let ((a (car cursor)) (succ (cdr cursor)))
|
||||
(if (even? a)
|
||||
|
@ -284,6 +305,29 @@ Tests!
|
|||
|
||||
Finish documentation.
|
||||
|
||||
Figure out if I can do anything about branching. I would love to remove the body and just have loop clauses. I don't think I can do that without some serious voodoo if I want to keep the current syntax. One idea would be to define all accumulators in the start of the loop, and then bind identifiers using local macros:
|
||||
|
||||
``` scheme
|
||||
(loop (accumulators ...)
|
||||
(clauses ...)
|
||||
=> final-expr)
|
||||
|
||||
(loop ((vectoring a)
|
||||
(listing b))
|
||||
(:for i (up-from 1 11))
|
||||
(:save b (* i i))
|
||||
(:if (odd? i)
|
||||
(:subloop (:for ab (in-list '(a b)))
|
||||
(:save a (cons i ab)))
|
||||
(:subloop (:for cd (in-list '(c d)))
|
||||
(:save a (cons i cd))))
|
||||
(:save a 'next))
|
||||
(1 4 9 16 ...)
|
||||
#((1 . a) (1 . b) next (2 . c) (2 . d) next ...)
|
||||
```
|
||||
|
||||
But this solution isn't great. The current situation isn't either, though. The body is executed AFTER all other clauses and is only really useful for things like branching, which would be much nicer to have in the clauses. The few times one wants a right fold, a simple :body clause will do the trick.
|
||||
|
||||
## foof, what a guy
|
||||
I have previously expressed some admiration for Alex Shinn and I will do it again. The source of chibi loop is extremely elegant, and all but the hairiest part is written in syntax-rules. Not only has he written my two favourite SRFIs, his input in all the other discussions I have seen is always on-point, pragmatic and generally fantastic. He neither knows of this project, nor embraces it in any way. Y'all should go look at the source of (chibi loop) though.
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
<li>A looping facility that in almost all cases produces as fast code as a hand-written named let</li>
|
||||
<li>An extensible looping facility, where new ways of iterating over data can be easily added</li>
|
||||
</ul>
|
||||
<p>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.</p>
|
||||
<p>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</p>
|
||||
<p>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.</p>
|
||||
<div id="An example or two"><h3>An example or two</h3><p>So, how does it look? A slightly contrived example, a naive sieve of Erathostenes:</p>
|
||||
<pre class="code-example">
|
||||
(define (erathostenes n)
|
||||
|
@ -19,22 +20,20 @@
|
|||
(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 (* 3 i) (:to n) (:by (* i 2)))))
|
||||
(loop ((:for j (up-from (* 2 i) (:to n) (:by i)))
|
||||
(vector-set! vec j #f))
|
||||
i))
|
||||
</pre><p>Calling <code>(erathostenes 10)</code> returns a list of all primes below 10.</p>
|
||||
<p>The example above can also be written using “subloops”, but unless you know the expansion it can be somewhat surprising.</p>
|
||||
<p>The example above can also be written using “subloops”:</p>
|
||||
<pre class="code-example">
|
||||
(define (erathostenes n)
|
||||
(define vec (make-vector n #t))
|
||||
(loop ((:for i (up-from 2 (:to n)))
|
||||
(:acc lst (listing i))
|
||||
(:when (vector-ref vec i))
|
||||
(:for j (up-from (* 3 i) (:to n) (:by (* i 2))))
|
||||
=> lst
|
||||
(:acc lst (listing i))
|
||||
(:for j (up-from (* 2 i) (:to n) (:by i))))
|
||||
(vector-set! vec j #f)))
|
||||
</pre><p>Any :for clause following a :break, :when, :unless or :final clause is considered to be a subloop. Any :when clause also affects when accumulating clauses collect values. The expression following => is the final expression: this is the expression returned after the loop ends.</p>
|
||||
</div></div><div id="Specification"><h2>Specification</h2><p>The loop grammar is the following:</p>
|
||||
</pre></div></div><div id="Specification"><h2>Specification</h2><p>The loop grammar is the following:</p>
|
||||
<pre class="code-example">
|
||||
(loop [name] (loop-clause ...) [=> final-expr] body ...)
|
||||
|
||||
|
@ -42,15 +41,19 @@
|
|||
|
||||
loop-clause = (:for id id* ... seq-expr)
|
||||
| (:acc id id* ... seq-expr)
|
||||
| (:let id expr)
|
||||
| (:let* id 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.
|
||||
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.
|
||||
</pre><p>If a <code>name</code> is provided, it will be bound to a macro that allows named update of loop variables:</p>
|
||||
<pre class="code-example">
|
||||
(loop lp ((:for a (in 0 (+ a 1)))
|
||||
|
@ -60,13 +63,12 @@
|
|||
(cons a (lp (=> a 8)))
|
||||
(cons a (lp))))
|
||||
</pre><p>This rather inane example would return <code>(0 1 2 3 4 8 9)</code>. Read more about this in the part about loop variables.</p>
|
||||
<div id="Subloops"><h3>Subloops</h3><p>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. All non-binding clauses break out a subloop.</p>
|
||||
<div id="Subloops"><h3>Subloops</h3><p>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.</p>
|
||||
<pre class="code-example">
|
||||
(loop ((:for a (in-list '(1 2 3)))
|
||||
:subloop
|
||||
(:for b (up-from 0 (to a)))
|
||||
(:acc acc (listing (cons a b))))
|
||||
=> acc)
|
||||
(:acc acc (listing (cons a b)))))
|
||||
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
|
||||
</pre><p>The above <code>:subloop</code> clause is equivalent to <code>:when #t</code> and <code>:unless #f</code>. A <code>:break</code> clause will immediately stop execution of the loop:</p>
|
||||
|
@ -74,20 +76,32 @@
|
|||
(loop ((:for a (in-list '(1 2 3)))
|
||||
(:break (= 3 a))
|
||||
(:for b (up-from 0 (to a)))
|
||||
(:acc acc (listing (cons a b))))
|
||||
=> acc)
|
||||
(:acc acc (listing (cons a b)))))
|
||||
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1))
|
||||
</pre><p>And a :final guard will let one more body be evaluated. This clause is evaluated in the innermost loop, and as such only one body will be evaluated:</p>
|
||||
</pre><p>And a :final guard will let the subsequent subloops execute once.</p>
|
||||
<pre class="code-example">
|
||||
(loop ((:for a (in-list '(1 2 3)))
|
||||
(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))))
|
||||
=> acc)
|
||||
(:acc acc (listing (cons a b)))))
|
||||
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0))
|
||||
</pre></div><div id="Loop variables"><h3>Loop variables</h3><p>Both accumulating clauses and :for clauses have something called loop variables. In the case of <code>(:for elt (in-list lst))</code> the loop variable would be the current pair where <code>elt</code> is the car. Some :acc- or :for-clauses may expose their loop variables so that they can be queried or even updated.</p>
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1))
|
||||
</pre><p>The :final clause is actually equivalent to something alike the following:</p>
|
||||
<pre class="code-example">
|
||||
(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)))))
|
||||
</pre><p>This means that any clause above the final binding will be executed an extra time before the loop exits:</p>
|
||||
<pre class="code-example">
|
||||
(loop ((:for a (up-from 1 4))
|
||||
(:acc lst (listing a))
|
||||
(:final (= a 2))
|
||||
(:acc lst2 (listing a))))
|
||||
;; => (1 2 3) (1 2)
|
||||
</pre></div><div id="Loop variables"><h3>Loop variables</h3><p>Both accumulating clauses and :for clauses have something called loop variables. In the case of <code>(:for elt (in-list lst))</code> the loop variable would be the current pair where <code>elt</code> is the car. Some :acc- or :for-clauses may expose their loop variables so that they can be queried or even updated.</p>
|
||||
<p>In the case of the menioned <code>in-list</code> we can choo se to expose the name of the current pair, as in the following example:</p>
|
||||
<pre class="code-example">
|
||||
(define (interpose lst between)
|
||||
|
@ -152,13 +166,8 @@
|
|||
</dd></dt><dt><a id="stop-after"><b>Scheme syntax: </b>stop-after</a><dd><code>(:for binding (stop-after iterator pred))</code><br /><p>Binds <code>binding</code> to the values produced by <code>iterator</code> until <code>pred</code> 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.</p>
|
||||
</dd></dt></dl></div><div id=":acc-clauses"><h3>:acc-clauses</h3><p>Accumulating clauses differ from :for-clauses in 2 significant ways. They have a final value available in the <code>final-expr</code>, 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.</p>
|
||||
<p>Another small thing is that for some :acc-clauses, the <code>binding</code> may sometimes only be visible to the user in the <code>final-expr</code>, but like :for-clauses they sometimes offer the programmer to name the loop variables.</p>
|
||||
<p>One general thing about accumulating clauses is that they all support a guarding <code>if</code> form. If such a clause is given, accumulation will only happen if the guard clause returns true. When a <code>:when</code> or <code>:unless</code> clause is given, they also have to return true for any result to be accumulated. The following code returns the empty list:</p>
|
||||
<pre class="code-example">
|
||||
(loop ((:for a (up-from 0 10))
|
||||
(:acc acc (listing a (if (odd? a))))
|
||||
(:when (even? a)))
|
||||
=> acc)
|
||||
</pre><dl><dt><a id="listing"><b>Scheme syntax: </b>listing</a><dd><code>(:acc binding (listing [(initial init)] expr [if guard]))</code><br /><p>Accumulates <code>expr</code> into a list. ´binding<code>is only accesible in the final-expression. The list is in the same order as the loop bodies were evaluated. If</code>initial<code>is given that will be used as the tail of the accumulated results. It defaults to</code>’()`.</p>
|
||||
<p>Many accumulating clauses support an <code>if</code> form. If such a clause is given, accumulation will only happen if the guard clause returns true.</p>
|
||||
<dl><dt><a id="listing"><b>Scheme syntax: </b>listing</a><dd><code>(:acc binding (listing [(initial init)] expr [if guard]))</code><br /><p>Accumulates <code>expr</code> into a list. ´binding<code>is only accesible in the final-expression. The list is in the same order as the loop bodies were evaluated. If</code>initial<code>is given that will be used as the tail of the accumulated results. It defaults to</code>’()`.</p>
|
||||
</dd></dt><dt><a id="listing-reverse"><b>Scheme syntax: </b>listing-reverse</a><dd><code>(:acc binding (listing-reverse [(initial init)] expr [if guard]))</code><br /><p>The same as <code>listing</code> 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.</p>
|
||||
</dd></dt><dt><a id="appending"><b>Scheme syntax: </b>appending</a><dd><code>(:acc binding (appending [(initial init)] expr [if guard]))</code><br /><p><code>expr</code> evaluates to a list that is then appended to the accumulated result.</p>
|
||||
<pre class="code-example">
|
||||
|
@ -174,11 +183,13 @@
|
|||
;; => (4 3 2 1 0)
|
||||
</pre></dd></dt><dt><a id="summing"><b>Scheme syntax: </b>summing</a><dd><code>(:acc binding (summing [(initial init)] expr [(if guard)]))</code><br /><p>Adds the result of <code>expr</code> together using <code>+</code>. The default initial value is 0.</p>
|
||||
</dd></dt><dt><a id="multiplying"><b>Scheme syntax: </b>multiplying</a><dd><code>(:acc binding (multiplying [(initial init)] expr [(if guard)]))</code><br /><p>Multiplies the result of <code>expr</code> using <code>*</code>. The default initial value is 1.</p>
|
||||
</dd></dt><dt><a id="hashing"><b>Scheme syntax: </b>hashing</a><dd><code>(:acc binding (hashing [(initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key => value)</code> to the hashtable <code>binding</code> using equal?-hashing. The initial hash table is an empty hash-table.</p>
|
||||
</dd></dt><dt><a id="hashving"><b>Scheme syntax: </b>hashving</a><dd><code>(:acc binding (hashving [(initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key => value)</code> to the hashtable <code>binding</code> using eqv?-hashing. The initial hash table is an empty hash-table.</p>
|
||||
</dd></dt><dt><a id="hashqing"><b>Scheme syntax: </b>hashqing</a><dd><code>(:acc binding (hashqing [(initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key => value)</code> to a hashtable using eq?-hashing. The initial hash table is an empty hash-table.</p>
|
||||
</dd></dt><dt><a id="vectoring"><b>Scheme syntax: </b>vectoring</a><dd><code>(:acc var [index] (vectoring expr [(:length len) [(:fill fill)]]))</code><br /><p>Accumulates the result of <code>expr</code> into a vector. If <code>len</code> and <code>fill</code> is given the vector will be at most <code>len</code> elements long and any unfilled indexes will contain the element <code>fill</code>. The loop will exit when <code>len</code> elements have been accumulated.</p>
|
||||
</dd></dt><dt><a id="hashing"><b>Scheme syntax: </b>hashing</a><dd><code>(:acc binding (hashing [(initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key => value)</code> to the hashtable <code>binding</code> using equal?-hashing. The initial hash table is an empty hash-table. <code>binding</code> is bound to the hash table throughout the loop, and its content can be mutated in the loop body.</p>
|
||||
</dd></dt><dt><a id="hashving"><b>Scheme syntax: </b>hashving</a><dd><code>(:acc binding (hashving [(initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key => value)</code> to the hashtable <code>binding</code> using eqv?-hashing. The initial hash table is an empty hash-table. <code>binding</code> is bound to the hash table throughout the loop, and its content can be mutated in the loop body.</p>
|
||||
</dd></dt><dt><a id="hashqing"><b>Scheme syntax: </b>hashqing</a><dd><code>(:acc binding (hashqing [(initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key => value)</code> to a hashtable using eq?-hashing. The initial hash table is an empty hash-table.<code>binding</code> is bound to the hash table throughout the loop, and its can be mutated in the loop body.</p>
|
||||
</dd></dt><dt><a id="vectoring"><b>Scheme syntax: </b>vectoring</a><dd><code>(:acc binding [index] (vectoring expr [(:length len) [(:fill fill)]]))</code><br /><p>Accumulates the result of <code>expr</code> into a vector. If <code>len</code> and <code>fill</code> is given the vector will be at most <code>len</code> elements long and any unfilled indexes will contain the element <code>fill</code>. The loop will exit when <code>len</code> elements have been accumulated.</p>
|
||||
<p>If <code>length</code> is not given, the vector will be expanded as required.</p>
|
||||
<p>A vectoring clause adds an implicit <code>(:break (= index len))</code> 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.</p>
|
||||
<p><code>binding</code> is bound to the vector throughout the loop, and its content mutated in the loop body.</p>
|
||||
</dd></dt></dl></div></div><div id="Loop protocol"><h2>Loop protocol</h2><p>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.</p>
|
||||
<div id=":for-clauses"><h3>:for-clauses</h3><p>The following example defines the simple :for-driver <code>in-alist</code>:</p>
|
||||
<pre class="code-example">
|
||||
|
@ -206,7 +217,7 @@
|
|||
</pre><p>In short, the clause (:for key value (in-alist alist-expr)) expands to:</p>
|
||||
<pre class="code-example">
|
||||
(in-alist ((key val) (alist-expr)) next-macro . rest)
|
||||
</pre><p>You almost never have to care about <code>rest</code>. 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. (If you want to see how that is done, have a look at the source of <code>vectoring</code> which (ab)uses this to introduce a :break clause without breaking out a subloop).</p>
|
||||
</pre><p>You should never have to care about <code>rest</code>. 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.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
|
@ -251,17 +262,46 @@
|
|||
FOR-CLAUSE-FINALIZER ...
|
||||
(final-function (ACCUMULATOR-FINALIZER ACCUMULATOR) ...))
|
||||
(let ((BODY-BINDING ... BODY-BINDING-EXPR) ...)
|
||||
(let ((USER-BINDING ...USER-BINDING-EXPR) ...)
|
||||
(match-let ((PARENTHESISED-PATTERN MATCH-EXPR))
|
||||
(if (and WHEN-EXPR ...)
|
||||
(cond
|
||||
((or USER-BREAK ...)
|
||||
FOR-CLAUSE-FINALIZER ...
|
||||
(final-function (ACCUMULATOR-FINALIZER ACCUMULATOR) ...))
|
||||
(else
|
||||
LOOP-BODY
|
||||
(goof-loop ACCUMULATE ... LOOP-VAR-NEXT ...))
|
||||
(goof-loop ACCUMULATOR ... LOOP-VAR-NEXT ...)))))))))
|
||||
(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)
|
||||
|
||||
</pre><p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
|
@ -269,11 +309,10 @@
|
|||
<p>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.</p>
|
||||
<p>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 …).</p>
|
||||
<p>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.</p>
|
||||
<p>PARENTHESISED-PATTERN and MATCH-EXPR: If a USER-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.</p>
|
||||
<p>WHEN-EXPR: the user supplied :when or :unless guard expression.</p>
|
||||
<p>USER-BREAK: user-supplied :break guard.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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 <code>(:bind (one (fst . snd) (values 1 (cons 3 4))))</code>, and it will work as expected.</p>
|
||||
<p>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.</p>
|
||||
</div><div id="Porting"><h2>Porting</h2><p>The bulk of goof-loop is written in portable syntax-rules. That code can be found in <code>goof-impl.scm</code> and all files under the <code>goof</code> 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:</p>
|
||||
<pre class="code-example">
|
||||
|
|
|
@ -26,7 +26,9 @@
|
|||
* A looping facility that in almost all cases produces as fast code as a hand-written named let
|
||||
* An extensible looping facility, where new ways of iterating over data can be easily added
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
<subsection title="An example or two">
|
||||
So, how does it look? A slightly contrived example, a naive sieve of Erathostenes:
|
||||
|
@ -37,27 +39,25 @@
|
|||
(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 (* 3 i) (:to n) (:by (* i 2)))))
|
||||
(loop ((:for j (up-from (* 2 i) (:to n) (:by i)))
|
||||
(vector-set! vec j #f))
|
||||
i))
|
||||
</example>
|
||||
|
||||
Calling `(erathostenes 10)` returns a list of all primes below 10.
|
||||
|
||||
The example above can also be written using "subloops", but unless you know the expansion it can be somewhat surprising.
|
||||
The example above can also be written using "subloops":
|
||||
|
||||
<example>
|
||||
(define (erathostenes n)
|
||||
(define vec (make-vector n #t))
|
||||
(loop ((:for i (up-from 2 (:to n)))
|
||||
(:acc lst (listing i))
|
||||
(:when (vector-ref vec i))
|
||||
(:for j (up-from (* 3 i) (:to n) (:by (* i 2))))
|
||||
=> lst
|
||||
(:acc lst (listing i))
|
||||
(:for j (up-from (* 2 i) (:to n) (:by i))))
|
||||
(vector-set! vec j #f)))
|
||||
</example>
|
||||
|
||||
Any :for clause following a :break, :when, :unless or :final clause is considered to be a subloop. Any :when clause also affects when accumulating clauses collect values. The expression following => is the final expression: this is the expression returned after the loop ends.
|
||||
|
||||
</subsection>
|
||||
</section>
|
||||
|
@ -72,15 +72,19 @@
|
|||
|
||||
loop-clause = (:for id id* ... seq-expr)
|
||||
| (:acc id id* ... seq-expr)
|
||||
| (:let id expr)
|
||||
| (:let* id 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.
|
||||
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.
|
||||
</example>
|
||||
|
||||
If a `name` is provided, it will be bound to a macro that allows named update of loop variables:
|
||||
|
@ -97,14 +101,13 @@
|
|||
This rather inane example would return `(0 1 2 3 4 8 9)`. Read more about this in the part about loop variables.
|
||||
|
||||
<subsection title="Subloops">
|
||||
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. All non-binding clauses break out a subloop.
|
||||
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.
|
||||
|
||||
<example>
|
||||
(loop ((:for a (in-list '(1 2 3)))
|
||||
:subloop
|
||||
(:for b (up-from 0 (to a)))
|
||||
(:acc acc (listing (cons a b))))
|
||||
=> acc)
|
||||
(:acc acc (listing (cons a b)))))
|
||||
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
|
||||
</example>
|
||||
|
@ -115,23 +118,41 @@
|
|||
(loop ((:for a (in-list '(1 2 3)))
|
||||
(:break (= 3 a))
|
||||
(:for b (up-from 0 (to a)))
|
||||
(:acc acc (listing (cons a b))))
|
||||
=> acc)
|
||||
(:acc acc (listing (cons a b)))))
|
||||
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1))
|
||||
</example>
|
||||
|
||||
And a :final guard will let one more body be evaluated. This clause is evaluated in the innermost loop, and as such only one body will be evaluated:
|
||||
And a :final guard will let the subsequent subloops execute once.
|
||||
|
||||
<example>
|
||||
(loop ((:for a (in-list '(1 2 3)))
|
||||
(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))))
|
||||
=> acc)
|
||||
(:acc acc (listing (cons a b)))))
|
||||
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0))
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1))
|
||||
</example>
|
||||
|
||||
The :final clause is actually equivalent to something alike the following:
|
||||
|
||||
<example>
|
||||
(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)))))
|
||||
</example>
|
||||
|
||||
This means that any clause above the final binding will be executed an extra time before the loop exits:
|
||||
|
||||
<example>
|
||||
(loop ((:for a (up-from 1 4))
|
||||
(:acc lst (listing a))
|
||||
(:final (= a 2))
|
||||
(:acc lst2 (listing a))))
|
||||
;; => (1 2 3) (1 2)
|
||||
</example>
|
||||
</subsection>
|
||||
|
||||
<subsection title="Loop variables">
|
||||
|
@ -367,14 +388,7 @@
|
|||
|
||||
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.
|
||||
|
||||
One general thing about accumulating clauses is that they all support a guarding `if` form. If such a clause is given, accumulation will only happen if the guard clause returns true. When a `:when` or `:unless` clause is given, they also have to return true for any result to be accumulated. The following code returns the empty list:
|
||||
|
||||
<example>
|
||||
(loop ((:for a (up-from 0 10))
|
||||
(:acc acc (listing a (if (odd? a))))
|
||||
(:when (even? a)))
|
||||
=> acc)
|
||||
</example>
|
||||
Many accumulating clauses support an `if` form. If such a clause is given, accumulation will only happen if the guard clause returns true.
|
||||
|
||||
<spec>
|
||||
<syntax name="listing">
|
||||
|
@ -386,8 +400,7 @@
|
|||
<syntax name="listing-reverse">
|
||||
<form>(:acc binding (listing-reverse [(initial init)] expr [if guard]))</form>
|
||||
|
||||
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.
|
||||
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.
|
||||
</syntax>
|
||||
|
||||
<syntax name="appending">
|
||||
|
@ -431,27 +444,31 @@
|
|||
<syntax name="hashing">
|
||||
<form>(:acc binding (hashing [(initial init)] key value [(if guard)]))</form>
|
||||
|
||||
Adds the mapping `(key => value)` to the hashtable `binding` using equal?-hashing. The initial hash table is an empty hash-table.
|
||||
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.
|
||||
</syntax>
|
||||
|
||||
<syntax name="hashving">
|
||||
<form>(:acc binding (hashving [(initial init)] key value [(if guard)]))</form>
|
||||
|
||||
Adds the mapping `(key => value)` to the hashtable `binding` using eqv?-hashing. The initial hash table is an empty hash-table.
|
||||
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.
|
||||
</syntax>
|
||||
|
||||
<syntax name="hashqing">
|
||||
<form>(:acc binding (hashqing [(initial init)] key value [(if guard)]))</form>
|
||||
|
||||
Adds the mapping `(key => value)` to a hashtable using eq?-hashing. The initial hash table is an empty hash-table.
|
||||
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.
|
||||
</syntax>
|
||||
|
||||
<syntax name="vectoring">
|
||||
<form>(:acc var [index] (vectoring expr [(:length len) [(:fill fill)]]))</form>
|
||||
<form>(:acc binding [index] (vectoring expr [(:length len) [(:fill fill)]]))</form>
|
||||
|
||||
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.
|
||||
</syntax>
|
||||
</spec>
|
||||
</subsection>
|
||||
|
@ -492,7 +509,7 @@
|
|||
(in-alist ((key val) (alist-expr)) next-macro . rest)
|
||||
</example>
|
||||
|
||||
You almost 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. (If you want to see how that is done, have a look at the source of `vectoring` which (ab)uses this to introduce a :break clause without breaking out a subloop).
|
||||
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.
|
||||
|
||||
|
@ -559,44 +576,71 @@
|
|||
FOR-CLAUSE-FINALIZER ...
|
||||
(final-function (ACCUMULATOR-FINALIZER ACCUMULATOR) ...))
|
||||
(let ((BODY-BINDING ... BODY-BINDING-EXPR) ...)
|
||||
(let ((USER-BINDING ...USER-BINDING-EXPR) ...)
|
||||
(match-let ((PARENTHESISED-PATTERN MATCH-EXPR))
|
||||
(if (and WHEN-EXPR ...)
|
||||
(cond
|
||||
((or USER-BREAK ...)
|
||||
FOR-CLAUSE-FINALIZER ...
|
||||
(final-function (ACCUMULATOR-FINALIZER ACCUMULATOR) ...))
|
||||
(else
|
||||
LOOP-BODY
|
||||
(goof-loop ACCUMULATE ... LOOP-VAR-NEXT ...))
|
||||
(goof-loop ACCUMULATOR ... LOOP-VAR-NEXT ...)))))))))
|
||||
(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)
|
||||
|
||||
</example>
|
||||
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.
|
||||
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.
|
||||
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? ...)).
|
||||
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.
|
||||
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 ...).
|
||||
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.
|
||||
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 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.
|
||||
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.
|
||||
|
||||
WHEN-EXPR: the user supplied :when or :unless guard expression.
|
||||
|
||||
USER-BREAK: user-supplied :break guard.
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
</section>
|
||||
<section title="Porting">
|
||||
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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue