New documentation

Added a "Porting" section.

Some clarifications.
This commit is contained in:
Linus 2021-03-11 14:42:52 +01:00
parent f0900a0497
commit 90c2c6bfdf
2 changed files with 53 additions and 3 deletions

View file

@ -73,7 +73,7 @@
;; => ((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>
<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>
<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)
(loop lp ((:for elt pair (in-list lst)))
@ -85,6 +85,7 @@
(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>
<p>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.</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)))
@ -265,4 +266,22 @@
In case of subloops, those are placed instead of &lt;loop-body&gt;. They use the same final-function, and instead of quitting when any &lt;check&gt; triggers they go out to the outer loop.
</pre></div></body></html>
</pre></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">
(loop lp ((:for a (up-from 0 10)))
=&gt; '()
(let ((a (+ a 1)))
(if (odd? a)
(cons a (lp (=&gt; a (+ a 2))))
(lp))))
;; =&gt; (1 5 9)
</pre><p>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.</p>
<p>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.</p>
<p>The third part is also a comfort macro; <code>valid-clause?</code>. 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:</p>
<pre class="code-example">
(define-syntax valid-clause?
(syntax-rules ()
((_ rest ... ) (rest ...))))
</pre><p>at the expense of error messages that become, for lack of a better word, ultra-shit. The <code>register-loop-clause</code> 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.</p>
<p>If your scheme of choice lacks these kinds of macrobatics, a simple symbol list helps a little bit, and is regarded as conformant.</p>
</div></body></html>

View file

@ -116,7 +116,7 @@
<subsection title="Loop variables">
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:
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:
<example>
(define (interpose lst between)
@ -131,6 +131,8 @@
</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.
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.
</subsection>
@ -545,4 +547,33 @@
In case of subloops, those are placed instead of &lt;loop-body&gt;. They use the same final-function, and instead of quitting when any &lt;check&gt; triggers they go out to the outer loop.
</verbatim>
</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:
<example>
(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)
</example>
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:
<example>
(define-syntax valid-clause?
(syntax-rules ()
((_ rest ... ) (rest ...))))
</example>
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.
</section>
</doc>