Fixed the documentation a bit

Added some more info. It still looks awful.
This commit is contained in:
Linus 2021-03-07 22:19:13 +01:00
parent bb0de3e949
commit 74514bb4d1
3 changed files with 111 additions and 50 deletions

View file

@ -127,7 +127,9 @@
(style
,(string-append
"body { max-width: 7.6in; margin: 30pt;} "
"pre { white-space: pre-wrap; }")))
"pre { white-space: pre-wrap; }"
".code-example { background: whitesmoke; padding: 3pt 3pt 0pt 3pt; border: 1px solid black }"
)))
(body
,html-title
(div (@ (id "authors"))

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html><head><title>Goof-loop</title><style>body { max-width: 7.6in; margin: 30pt;} pre { white-space: pre-wrap; }</style></head><body><h1>Goof-loop</h1><div id="authors"><a href="mailto:linus.internet@fastmail.se">Linus Björnstam</a></div><div id="Preface"><h2>Preface</h2><p>We have all had the thought. <em>Why oh why are not all our problems solved by a nice recursive algorithm over a list?</em>. 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.</p>
<html><head><title>Goof-loop</title><style>body { max-width: 7.6in; margin: 30pt;} pre { white-space: pre-wrap; }.code-example { background: whitesmoke; padding: 3pt 3pt 0pt 3pt; border: 1px solid black }</style></head><body><h1>Goof-loop</h1><div id="authors"><a href="mailto:linus.internet@fastmail.se">Linus Björnstam</a></div><div id="Preface"><h2>Preface</h2><p>We have all had the thought. <em>Why oh why are not all our problems solved by a nice recursive algorithm over a list?</em>. 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.</p>
<p>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?</p>
<p>We think again about how python programmers write scheme, and shudder. <em>No! Never that!</em>, we think to ourselves. A vague idea starts forming. A quote surfaces from the depths of our minds <em>“Above all the wonders of Lisps pantheon stand its metalinguistic tools; by their grace have Lisps acolytes been liberated from the rigid ascetism of lesser faiths”</em>. 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 couldnt be bothered to go any further. But a looping facility… How hard can it be?</p>
<p> Linus Björnstam, September 2020</p>
@ -16,68 +16,83 @@
(define (erathostenes n)
(define vec (make-vector n #t))
(loop/list ((:for i (up-from 2 (to n)))
:when (vector-ref vec i))
(:when (vector-ref vec i)))
(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>
</div></div><div id="Specification"><h2>Specification</h2><p>The loop grammar is the following:</p>
<pre>
<pre class="code-example">
(loop [name] (loop-clause ...) [=&gt; final-expr] body ...)
name = identifier
loop-clause = (:for id id* ... seq-expr)
| (:acc id id* ... seq-expr)
| :when guard-expr
| :unless guard-expr
| :break break-expr
| :final guard-expr
| (:when guard-expr)
| (:unless guard-expr)
| (:break break-expr)
| (:final guard-expr)
| :subloop
seq-expr = a macro that conforms to the looping protocol described below.
</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)))
:break (&gt; a 9))
(:break (&gt; a 9)))
=&gt; '()
(if (= 4 a)
(cons a (lp (=&gt; a 8)))
(cons a (lp))))
</pre><p>This rather inane example would return <code>(0 1 2 3 4 8 9)</code>.</p>
<div id="Subloops"><h3>Subloops</h3><p>A subloop is a distinction between an outer loop and an inner loop. An for each element yielded by an outer loop, the inner loop is run until exhaustion. All non :for- or :acc-clauses break out a subloop.</p>
</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>
<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))))
=&gt; acc)
;; =&gt; ((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>
<pre class="code-example">
(loop ((:for a (in-list '(1 2 3)))
:break (= 3 a)
(:for b (up-from 0 (to a)))
(:acc acc (listing (cons a b))))
(:break (= 3 a))
(:for b (up-from 0 (to a)))
(:acc acc (listing (cons a b))))
=&gt; acc)
;; =&gt; ((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 class="code-example">
(loop ((:for a (in-list '(1 2 3)))
:final (= 3 a)
(:for b (up-from 0 (to a)))
(:acc acc (listing (cons a b))))
(:final (= 3 a))
(:for b (up-from 0 (to a)))
(:acc acc (listing (cons a b))))
=&gt; acc)
;; =&gt; ((1 . 0) (2 . 0) (2 . 1) (3 . 0))
</pre></div><div id="Loop variables"><h3>Loop variables</h3><p>To be written</p>
</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 choose to expose the current pair, as in the following example:</p>
<pre class="code-example">
(define (interpose lst between)
(loop lp ((:for elt pair (in-list lst)))
=&gt; '() ;; reached only if lst is empty
(if (null? (cdr pair))
(list elt)
(cons* elt between (lp)))))
(interpose '(1 2 3 4) ':)
; =&gt; (1 : 2 : 3 : 4)
</pre><p>In the above example we chose to bind the loop variable of <code>in-list</code> to <code>pair</code>. Using <code>pair</code> we can then query if the next iteration is the empty list and decide not to interpose any value.</p>
</div><div id="Simple forms"><h3>Simple forms</h3><p>The pure <code>loop</code> 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.</p>
<pre class="code-example">
(loop/list ((count (up-from 0 (to 100)))
(a (in 0 b))
(b (in 1 (+ a b))))
b)
</pre><dl><dt><a id="loop/first"><b>Scheme syntax: </b>loop/first</a><dd><code>(loop/first (clauses ...) body ...)</code><br /><p>If any body is ever evaluated, stop and return the value of that evaluation. If no body is ever evaluated the return value is unspecified.</p>
</pre><p>The simple forms provided by goof-loop are the following:</p>
<dl><dt><a id="loop/first"><b>Scheme syntax: </b>loop/first</a><dd><code>(loop/first (clauses ...) body ...)</code><br /><p>If any body is ever evaluated, stop and return the value of that evaluation. If no body is ever evaluated the return value is unspecified.</p>
</dd></dt><dt><a id="loop/last"><b>Scheme syntax: </b>loop/last</a><dd><code>(loop/last (clauses ...) body ...)</code><br /><p>Returns the result of the last body to be evaluated. If no body is evaluated the return value is unspecified.</p>
</dd></dt><dt><a id="loop/list"><b>Scheme syntax: </b>loop/list</a><dd><code>(loop/list (clauses ...) body ...)</code><br /><p>Iterates over <code>clauses</code> 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.</p>
<p>The list returned is the same even when used with multi-shot continuations.</p>
@ -92,14 +107,18 @@
(loop ((:for a (in 0 b)) (:for b (in 1 (+ a b) (&gt; b 20))))
(display b) (newline))
</pre><p>Will print all fibonacci numbers below 20.</p>
</dd></dt><dt><a id="up-from"><b>Scheme syntax: </b>up-from</a><dd><code>(:for binding (up-from start [(to bound)] [(by step)]))</code><br /><code>(:for binding (up-from start [bound [by]]))</code><br /><p>Binds <code>binding</code> to the number <code>start</code> up to <code>bound</code> (exclusive!) by <code>step</code>. If no <code>bound</code> is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteratiom.</p>
</dd></dt><dt><a id="down-from"><b>Scheme syntax: </b>down-from</a><dd><code>(:for binding (down-from start [(to bound)] [(by step)])</code><br /><code>(:for binding (down-from start [bound [by]]))</code><br /><p>Binds <code>binding</code> to the number <code>(- start 1)</code> down to <code>bound</code> (inclusive!) by <code>step</code>. If no <code>bound</code> is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteratiom.</p>
</dd></dt><dt><a id="up-from"><b>Scheme syntax: </b>up-from</a><dd><code>(:for binding (up-from start [(to bound)] [(by step)]))</code><br /><code>(:for binding (up-from start [bound [by]]))</code><br /><p>Binds <code>binding</code> to the number <code>start</code> up to <code>bound</code> (exclusive!) by <code>step</code>. If no <code>bound</code> is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteration if a <code>step</code> other than <code>1</code> is wanted.</p>
</dd></dt><dt><a id="down-from"><b>Scheme syntax: </b>down-from</a><dd><code>(:for binding (down-from start [(to bound)] [(by step)])</code><br /><code>(:for binding (down-from start [bound [by]]))</code><br /><p>Binds <code>binding</code> to the number <code>(- start 1)</code> down to <code>bound</code> (inclusive!) by <code>step</code>. If no <code>bound</code> is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteration if a <code>step</code> other than <code>1</code> is wanted.</p>
</dd></dt><dt><a id="in-list"><b>Scheme syntax: </b>in-list</a><dd><code>(:for binding [pair] (in-list expr [by])</code><br /><p>Binds <code>binding</code> to the car of the loop variable <code>pair</code>. <code>pair</code> is advanced by applying the procedure <code>by</code> to it (defaulting to <code>cdr</code>). The iteration stops when <code>pair</code> is the empty list.</p>
</dd></dt><dt><a id="in-lists"><b>Scheme syntax: </b>in-lists</a><dd><code>(:for binding [pairs] (in-lists expr [by])</code><br /><p>Works the same as <code>in-list</code>, but <code>expr</code> must evaluate to a list of lists. <code>binding</code> is bound to the car of those lists, and they are advanced by <code>by</code>, defaulting to <code>cdr</code>.</p>
</dd></dt><dt><a id="in-vector"><b>Scheme syntax: </b>in-vector</a><dd><code>(:for binding [index] (in-vector expr [low [high]]))</code><br /><p>Binds <code>binding</code> to all elements in the vector produced by <code>expr</code> in order from <code>low</code> to <code>high</code>. <code>low</code> defaults to 0 and <code>high</code> defaults to the last index of the vector.</p>
<p>The vector produced by <code>expr</code> is bound outside the loop body, and <code>index</code> is the loop variable.</p>
</dd></dt><dt><a id="in-reverse-vector"><b>Scheme syntax: </b>in-reverse-vector</a><dd><code>(:for binding [index] (in-reverse-vector expr [high [low]]))</code><br /><p>Binds <code>binding</code> to all elements in the vector produced by <code>expr</code> in reverse order from <code>high</code> to <code>low</code>. <code>high</code> defaults to the last element of the vector and <code>low</code> defaults to 0.</p>
<p>The vector produced by <code>expr</code> is bound outside the loop body, and <code>index</code> is the loop variable.</p>
</dd></dt><dt><a id="in-string"><b>Scheme syntax: </b>in-string</a><dd><code>(:for binding [index] (in-string expr [low [high]]))</code><br /><p>Binds <code>binding</code> to all elements in the string produced by <code>expr</code> in order from <code>low</code> to <code>high</code>. <code>low</code> defaults to 0 and <code>high</code> defaults to the last index of the string.</p>
<p>The string produced by <code>expr</code> is bound outside the loop body, and <code>index</code> is the loop variable.</p>
</dd></dt><dt><a id="in-reverse-string"><b>Scheme syntax: </b>in-reverse-string</a><dd><code>(:for binding [index] (in-reverse-string expr [high [low]]))</code><br /><p>Binds <code>binding</code> to all elements in the vector produced by <code>expr</code> in reverse order from <code>high</code> to <code>low</code>. <code>high</code> defaults to the last element of the vector and <code>low</code> defaults to 0.</p>
<p>The string produced by <code>expr</code> is bound outside the loop body, and <code>index</code> is the loop variable.</p>
</dd></dt><dt><a id="in-port"><b>Scheme syntax: </b>in-port</a><dd><code>(:for binding (in-port port [reader [eof?]]))</code><br /><p>Binds <code>binding</code> to the result of calling <code>reader</code> on <code>port</code>. Iteration stops when <code>(eof? binding)</code> returns true.</p>
</dd></dt><dt><a id="in-file"><b>Scheme syntax: </b>in-file</a><dd><code>(:for binding (in-file path [reader [eof?]]))</code><br /><p>Opens the file located at <code>path</code> (which is a string) and binds <code>binding</code> to the result of calling <code>reader</code> on the opened port. Iteration stops when <code>(eof? binding)</code> returns true.</p>
</dd></dt><dt><a id="in-generator"><b>Scheme syntax: </b>in-generator</a><dd><code>(:for binding (in-generator gen))</code><br /><p>Binds binding to the result of calling the SRFI-158-compatible generator <code>gen</code>. Iteration stops when <code>gen</code> returns the end-of-file object.</p>
@ -113,7 +132,7 @@
<pre class="code-example">
(loop ((:for a (up-from 0 10))
(:acc acc (listing a (if (odd? a))))
:when (even? a))
(:when (even? a)))
=&gt; 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>
</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>
@ -159,6 +178,7 @@
;; 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)
</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)
@ -168,6 +188,7 @@
<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>
<p>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. <code>(a b (div-and-mod 10 7))</code> is valid, and so is ((key . val) (let ((c (car %cursor))) (values (car c) (cdr c)))).</p>
<p>Finalizers are run everytime the current loop or any subloop below the current loop exits. This can be used to close resources. <code>(in-file ...)</code> uses this.</p>
<p>register-loop-clause registers the loop clause so that it can be verified to exist at expansion time.</p>
</div><div id=":acc-clauses"><h3>:acc-clauses</h3><p>The following code implements the accumulator (alisting …) which accumulates two values into an alist:</p>
<pre class="code-example">
(define-syntax alisting
@ -188,14 +209,17 @@
;; (let ((name (reverse %cursor))) ...)
((name (reverse %cursor)))
. rest))))
(register-loop-clause 'acc #'alisting)
</pre><p>The first difference is that the first argument to an accumulator always is the symbol :acc.</p>
<p>So, number one is the same as for :for-clauses. Number two looks the same, except the name %cursor and its 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 <em>cannot</em> therefore reference any things bound in outer loops.</p>
<p>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.</p>
<p>The body bindings can be used to bind variables to make information from the accumulator visible, but otherwise not used.</p>
<p>Final bindings. Binds whatever variable name you chose to whatever expression you chose in the final-expression.</p>
<p>register-loop-clause registers the alisting clause so that it can be verified to exist at expansion time.</p>
</div></div><div id="Loop expansion"><h2>Loop expansion</h2><p>The main chunk of a loop expands into something like the following: A goof loop expands into something looking like this:</p>
<pre>
(let* (&lt;outer-let&gt; ...
(let* (&lt;outer-binding&gt; ...
final-function (lambda (&lt;final-binding&gt;) &lt;final-expr&gt;))
(let goof-loop ((&lt;accumulator&gt; &lt;accumulator-init&gt;) ... (&lt;loop-var&gt; &lt;loop-var-init&gt;) ...)
(if (or &lt;check&gt; ...)
@ -215,7 +239,7 @@
(goof-loop &lt;accumulate&gt; ... &lt;loop-var-next&gt; ...))
(goof-loop &lt;accumulator&gt; ... &lt;loop-var-next&gt; ...)))))))))
&lt;outer-let&gt;: 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.
&lt;outer-binding&gt;: 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.
&lt;final-binding&gt; and &lt;final-expr&gt;: 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 &lt;final-expr&gt;. In case of (listing ...) the accumulated results are reversed before the final function.

View file

@ -7,7 +7,8 @@
<section title="Preface">
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 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?
@ -32,7 +33,7 @@
(define (erathostenes n)
(define vec (make-vector n #t))
(loop/list ((:for i (up-from 2 (to n)))
:when (vector-ref vec i))
(:when (vector-ref vec i)))
(loop ((:for j (up-from (* 2 i) (to n) (by i))))
(vector-set! vec j #f))
i))
@ -45,38 +46,37 @@
<section title="Specification">
The loop grammar is the following:
<verbatim>
<example>
(loop [name] (loop-clause ...) [=> final-expr] body ...)
name = identifier
loop-clause = (:for id id* ... seq-expr)
| (:acc id id* ... seq-expr)
| :when guard-expr
| :unless guard-expr
| :break break-expr
| :final guard-expr
| (:when guard-expr)
| (:unless guard-expr)
| (:break break-expr)
| (:final guard-expr)
| :subloop
seq-expr = a macro that conforms to the looping protocol described below.
</verbatim>
</example>
If a `name` is provided, it will be bound to a macro that allows named update of loop variables:
<example>
(loop lp ((:for a (in 0 (+ a 1)))
:break (> a 9))
(:break (> a 9)))
=> '()
(if (= 4 a)
(cons a (lp (=> a 8)))
(cons a (lp))))
</example>
This rather inane example would return `(0 1 2 3 4 8 9)`.
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. An for each element yielded by an outer loop, the inner loop is run until exhaustion. All non :for- or :acc-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. All non-binding clauses break out a subloop.
<example>
(loop ((:for a (in-list '(1 2 3)))
@ -84,6 +84,7 @@
(:for b (up-from 0 (to a)))
(:acc acc (listing (cons a b))))
=> acc)
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
</example>
@ -91,10 +92,11 @@
<example>
(loop ((:for a (in-list '(1 2 3)))
:break (= 3 a)
(:for b (up-from 0 (to a)))
(:acc acc (listing (cons a b))))
(:break (= 3 a))
(:for b (up-from 0 (to a)))
(:acc acc (listing (cons a b))))
=> acc)
;; => ((1 . 0) (2 . 0) (2 . 1))
</example>
@ -102,16 +104,33 @@
<example>
(loop ((:for a (in-list '(1 2 3)))
:final (= 3 a)
(:for b (up-from 0 (to a)))
(:acc acc (listing (cons a b))))
(:final (= 3 a))
(:for b (up-from 0 (to a)))
(:acc acc (listing (cons a b))))
=> acc)
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0))
</example>
</subsection>
<subsection title="Loop variables">
To be written
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 choose to expose the current pair, as in the following example:
<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)
</example>
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.
</subsection>
@ -124,6 +143,8 @@
(b (in 1 (+ a b))))
b)
</example>
The simple forms provided by goof-loop are the following:
<spec>
<syntax name="loop/first">
@ -201,14 +222,14 @@
<form>(:for binding (up-from start [(to bound)] [(by step)]))</form>
<form>(:for binding (up-from start [bound [by]]))</form>
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 iteratiom.
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.
</syntax>
<syntax name="down-from">
<form>(:for binding (down-from start [(to bound)] [(by step)])</form>
<form>(:for binding (down-from start [bound [by]]))</form>
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 iteratiom.
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.
</syntax>
<syntax name="in-list">
@ -227,24 +248,32 @@
<form>(:for binding [index] (in-vector expr [low [high]]))</form>
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.
</syntax>
<syntax name="in-reverse-vector">
<form>(:for binding [index] (in-reverse-vector expr [high [low]]))</form>
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.
</syntax>
<syntax name="in-string">
<form>(:for binding [index] (in-string expr [low [high]]))</form>
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.
</syntax>
<syntax name="in-reverse-string">
<form>(:for binding [index] (in-reverse-string expr [high [low]]))</form>
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.
</syntax>
<syntax name="in-port">
@ -289,7 +318,7 @@
<example>
(loop ((:for a (up-from 0 10))
(:acc acc (listing a (if (odd? a))))
:when (even? a))
(:when (even? a)))
=> acc)
</example>
@ -400,6 +429,7 @@
;; 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)
</example>
In short, the clause (:for key value (in-alist alist-expr)) expands to:
@ -419,6 +449,8 @@
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.
</subsection>
<subsection title=":acc-clauses">
@ -443,6 +475,8 @@
;; (let ((name (reverse %cursor))) ...)
((name (reverse %cursor)))
. rest))))
(register-loop-clause 'acc #'alisting)
</example>
The first difference is that the first argument to an accumulator always is the symbol :acc.
@ -454,7 +488,8 @@
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.
</subsection>
</section>
@ -462,7 +497,7 @@
The main chunk of a loop expands into something like the following:
A goof loop expands into something looking like this:
<verbatim>
(let* (&lt;outer-let&gt; ...
(let* (&lt;outer-binding&gt; ...
final-function (lambda (&lt;final-binding&gt;) &lt;final-expr&gt;))
(let goof-loop ((&lt;accumulator&gt; &lt;accumulator-init&gt;) ... (&lt;loop-var&gt; &lt;loop-var-init&gt;) ...)
(if (or &lt;check&gt; ...)
@ -482,7 +517,7 @@
(goof-loop &lt;accumulate&gt; ... &lt;loop-var-next&gt; ...))
(goof-loop &lt;accumulator&gt; ... &lt;loop-var-next&gt; ...)))))))))
&lt;outer-let&gt;: 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.
&lt;outer-binding&gt;: 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.
&lt;final-binding&gt; and &lt;final-expr&gt;: 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 &lt;final-expr&gt;. In case of (listing ...) the accumulated results are reversed before the final function.