Updated the documentation, added pointer to hosted version

This commit is contained in:
Linus 2021-05-11 13:27:09 +02:00
parent 66e435aa92
commit 5a92ba298d
3 changed files with 88 additions and 138 deletions

View file

@ -40,7 +40,9 @@ Accumulators can be in any of the loop's stages:
## Documentation
Is only available in a weird markdown/xml chimaera or in HTML format. This will change. You can find it in documentation doc.xml (for the weird format) and documentation/doc.html for the slightly more accessible HTML format. It looks ugly though.
The current WIP documentation can be found here: https://bjoli.srht.site/doc.html
It is written in a weird markdown/xml chimaera. You can find it in documentation doc.xml (for the weird format) and documentation/doc.html for the slightly more accessible HTML format.
## Differences from foof-loop
@ -55,6 +57,22 @@ while and until are removed in favour of :break.
with-clauses are removed in favour of (:for var (in init [step [stop]])) in case of loop clauses, or (:acc var (folding init [step])) in case of accumulators.
### Higher order loop protocol
goof supports a higher order looping protocol, based on srfi-158 generators:
(loop ((:for food (in-list '(banana cake grape cake bean cake)))
(:for true? (in-cycle (in-list '(#t #f)))))
(display "The ")
(display food)
(display " is a ")
(if true?
(display food)
(display "LIE!"))
(newline))
In the above example true? never ends, but restarts every time the list is exhausted.
### Regressions compared to foof-loop
only accumulating clauses are visible in the final-expression. This is due to sequence clauses not being promoted through to outer loops (since they should not keep their state if an inner loop is exited).
@ -71,7 +89,7 @@ Due to clause reordering, positional updates are not supported. If you want to u
### similarities
You can of course still have a larger control of your loops:
You can of course still have a larger control of when to loop by naming your loop:
```
(loop loopy-loop ((:for a (up-from 1 (to 11))))
@ -139,63 +157,9 @@ I also provide simplified forms for many common operations. Omitting :for is all
```
### Loop expansion
A goof loop expands into something looking like this:
```
(let* (<outer-let>)
(letrec ((final-function (lambda (<final-binding>) <final-expr>))
(goof-loop (lambda (<accumulator> ... <loop-var> ...)
(if (or <check> ...)
(begin
<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> ...))))))))
(goof-loop <accumulator-init> ... <loop-var-init> ...)))
```
&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;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.
&lt;accumulator&gt; and &lt;loop-variable&gt;: &lt;accumulator&gt; 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. &lt;loop-var&gt; is the current state of a :for clause.
&lt;check&gt;: Checks for :for-clauses. In the case of (in-list ...) this would check for (not (pair? ...)).
&lt;for-clause-finalizer&gt;: 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.
&lt;accumulator-finalizer&gt;: &lt;accumulator-finalizer&gt; is any preprocessing done to &lt;accumulator&gt; before passing it on to the final-function. In the case of (listing ...) that would be (reverse ...).
&lt;body-binding&gt; and &lt;body-binding-expr&gt;:&lt;body-binding&gt; 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.
&lt;parenthesised-pattern&gt; and &lt;match-expr&gt;: If a &lt;user-binding&gt; 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.
&lt;when-expr&gt;: the user supplied :when or :unless guard expression.
&lt;user-break&gt;: user-supplied :break guard.
&lt;loop-body&gt;, &lt;accumulate&gt;, and &lt;loop-var-next&gt;: 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. &lt;accumulate&gt; is the expression the accumulator clause provided to accumulate a new value. For (:acc acc (listing elem)) that is (cons elem acc). &lt;loop-var-next&gt; 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.
&lt;accumulator-init&gt; and &lt;loop-var-init&gt;: &lt;accumulator-init&gt; are ALL accumulator init values, including the ones in subloops. For (listing ...) that is the empty list. &lt;loop-var-init&gt; is the initial loop vars.
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.
### Speed
Speed is good. Despite the rather involved expansion above, due to dead-code elimination, the actual expansion shows some good code:
Speed is good. Despite the rather involved expansion you can see in the documentation, due to dead-code elimination, the actual expansion shows some good code:
```
> ,opt (loop ((:for a (in-list '(1 2 3 4)))
@ -210,17 +174,18 @@ $1 = (let loopy-loop ((cursor-1 '()) (cursor '(1 2 3 4)))
(loopy-loop cursor-1 succ)))
(reverse cursor-1)))
;; loop/list, being less general, produces faster code that can be more easily unroled and optimized.
;; loop/list, being less general, produces faster code that can be more easily optimized
> ,opt (loop/list ((a (in-list '(1 2 3 4)))
(:when (even? a)))
a)
$2 = (list 2 4)
;; Removing the opportunity to completely remove the loop
;; Removing the opportunity to completely optimize the loop away
> ,opt (loop/list ((a (in-list (read)))
(:when (even? a)))
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)))
(if (pair? cursor)
(let ((a (car cursor)) (succ (cdr cursor)))
@ -259,4 +224,4 @@ I have previously expressed some admiration for Alex and I will do it again. The
## Licence
The same BSD-styled license Alex uses for chibi-loop.
The same BSD-styled license Alex uses for chibi-loop.