From 5a92ba298d86ff4760adbd1165bf9fadee0a24b5 Mon Sep 17 00:00:00 2001 From: Linus Date: Tue, 11 May 2021 13:27:09 +0200 Subject: [PATCH] Updated the documentation, added pointer to hosted version --- README.md | 87 +++++++++++++----------------------------- documentation/doc.html | 76 +++++++++++++++--------------------- documentation/doc.xml | 63 +++++++++++++++--------------- 3 files changed, 88 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index 5309321..58a38c5 100644 --- a/README.md +++ b/README.md @@ -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* () - (letrec ((final-function (lambda () )) - (goof-loop (lambda ( ... ...) - (if (or ...) - (begin - ... - (final-function ( ) ...)) - (let (( ... ) ...) - (let (( ... ) ...) - (match-let (( )) - (if (and ...) - (cond - ((or ...) - ... - (final-function ( ) ...)) - (else - - (goof-loop ... ...)) - (goof-loop ... ...)))))))) - (goof-loop ... ...))) -``` - -<outer-let>: are provided by accumulators or for clauses for bindings that are not passed as an argument to the loop, for example a vector. The vector is bound here, and the index into the vector is the thing iterated over. - -<final-binding> and <final-expr>: When the iteration ends, this function is called with the results of the :acc clauses. In the case of (:acc lst-acc (listing ...)), the name of the accumulator is never lst-acc in the loop body, but only in the <final-expr>. In case of (listing ...) the accumulated results are reversed before the final function. - -<accumulator> and <loop-variable>: <accumulator> holds the current state of an accumulator clause. This is not necessarily the same binding as the user provided as the name, as described above. <loop-var> is the current state of a :for clause. - -<check>: Checks for :for-clauses. In the case of (in-list ...) this would check for (not (pair? ...)). - -<for-clause-finalizer>: some :for clauses need to be finalized. In the case of (in-file ...) the open file handle is closed at any point where the iteration stops. - -<accumulator-finalizer>: <accumulator-finalizer> is any preprocessing done to <accumulator> before passing it on to the final-function. In the case of (listing ...) that would be (reverse ...). - -<body-binding> and <body-binding-expr>:<body-binding> are the names the user provided for the body bindings. In the case of (:for a (in-list '(1 2 3))) the body binding would be (a (car name-of-loop-variable)). The body binding may be an (ice-9 match) pattern. More on that below. - -<parenthesised-pattern> and <match-expr>: If a <user-binding> 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. - -<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. - - -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. - ### 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. \ No newline at end of file +The same BSD-styled license Alex uses for chibi-loop. diff --git a/documentation/doc.html b/documentation/doc.html index 7cf1d18..3d1654d 100644 --- a/documentation/doc.html +++ b/documentation/doc.html @@ -242,54 +242,40 @@

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.

Loop expansion

The main chunk of a loop expands into something like the following: A goof loop expands into something looking like this:

-
-(let* (<outer-binding> ...
-       final-function (lambda (<final-binding>) <final-expr>))
-  (let goof-loop ((<accumulator> <accumulator-init>) ... (<loop-var> <loop-var-init>) ...)
-    (if (or <check> ...)
+
+(let* (OUTER-BINDING ...
+       final-function (lambda (FINAL-BINDING) FINAL-EXPR))
+  (let goof-loop ((ACCUMULATOR ACCUMULATOR-INIT) ... (LOOP-VAR LOOP-VAR-INIT) ...)
+    (if (or CHECK...)
       (begin
-        <for-clause-finalizer> ...
-        (final-function (<accumulator-finalizer> <accumulator>) ...))
-      (let ((<body-binding> ... <body-binding-expr>) ...)
-        (let ((<user-binding> ... <user-binding-expr>) ...)
-          (match-let ((<parenthesised-pattern> <match-expr>))
-            (if (and <when-expr> ...)            
+        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>) ...))
+                  ((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> ...)))))))))
-    
-<outer-binding>: are provided by accumulators or for clauses for bindings that are not passed as an argument to the loop, for example a vector. The vector is bound here, and the index into the vector is the thing iterated over.
-
-<final-binding> and <final-expr>: When the iteration ends, this function is called with the results of the :acc clauses. In the case of (:acc lst-acc (listing ...)), the name of the accumulator is never lst-acc in the loop body, but only in the <final-expr>. In case of (listing ...) the accumulated results are reversed before the final function.
-
-<accumulator> and  <loop-variable>: <accumulator> holds the current state of an accumulator clause. This is not necessarily the same binding as the user provided as the name, as described above. <loop-var> is the current state of a :for clause. 
-
-<check>: Checks for :for-clauses. In the case of (in-list ...) this would check for (not (pair? ...)).
-
-<for-clause-finalizer>: some :for clauses need to be finalized. In the case of (in-file ...) the open file handle is closed at any point where the iteration stops.
-
-<accumulator-finalizer>: <accumulator-finalizer> is any preprocessing done to <accumulator> before passing it on to the final-function. In the case of (listing ...) that would be (reverse ...).
-
-<body-binding> and <body-binding-expr>:<body-binding> are the names the user provided for the body bindings. In the case of (:for a (in-list '(1 2 3))) the body binding would be (a (car name-of-loop-variable)). The body binding may be an (ice-9 match) pattern. More on that below.
-
-<parenthesised-pattern> and <match-expr>: If a <user-binding> 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. 
-    
-<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.
-
-
-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.
-    

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:

+ LOOP-BODY + (goof-loop ACCUMULATE ... LOOP-VAR-NEXT ...)) + (goof-loop ACCUMULATOR ... LOOP-VAR-NEXT ...))))))))) +

OUTER-BINDING: are provided by accumulators or for clauses for bindings that are not passed as an argument to the loop, for example a vector. The vector is bound here, and the index into the vector is the thing iterated over.

+

FINAL-BINDING and FINAL-EXPR: When the iteration ends, this function is called with the results of the :acc clauses. In the case of (:acc lst-acc (listing …)), the name of the accumulator is never lst-acc in the loop body, but only in the FINAL-EXPR. In case of (listing …) the accumulated results are reversed before the final function.

+

ACCUMULATOR and LOOP-VAR: ACCUMULATOR holds the current state of an accumulator clause. This is not necessarily the same binding as the user provided as the name, as described above. LOOP-VAR is the current state of a :for clause.

+

CHECK: Checks for :for-clauses. In the case of (in-list …) this would check for (not (pair? …)).

+

FOR-CLAUSE-FINALIZER: some :for clauses need to be finalized. In the case of (in-file …) the open file handle is closed at any point where the iteration stops.

+

ACCUMULATOR-FINALIZER: ACCUMULATOR-FINALIZER is any preprocessing done to ACCUMULATOR before passing it on to the final-function. In the case of (listing …) that would be (reverse …).

+

BODY-BINDING and BODY-BINDING-EXPR: BODY-BINDING are the names the user provided for the body bindings. In the case of (:for a (in-list ’(1 2 3))) the body binding would be (a (car name-of-loop-variable)). The body binding may be an (ice-9 match) pattern. More on that below.

+

PARENTHESISED-PATTERN and MATCH-EXPR: If a USER-BINDING 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.

+

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.

+

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.

+

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:

 (loop lp ((:for a (up-from 0 10)))
   => '()
diff --git a/documentation/doc.xml b/documentation/doc.xml
index 3b9410b..04cf11d 100644
--- a/documentation/doc.xml
+++ b/documentation/doc.xml
@@ -550,54 +550,53 @@
   
The main chunk of a loop expands into something like the following: A goof loop expands into something looking like this: - - (let* (<outer-binding> ... - final-function (lambda (<final-binding>) <final-expr>)) - (let goof-loop ((<accumulator> <accumulator-init>) ... (<loop-var> <loop-var-init>) ...) - (if (or <check> ...) + + (let* (OUTER-BINDING ... + final-function (lambda (FINAL-BINDING) FINAL-EXPR)) + (let goof-loop ((ACCUMULATOR ACCUMULATOR-INIT) ... (LOOP-VAR LOOP-VAR-INIT) ...) + (if (or CHECK...) (begin - <for-clause-finalizer> ... - (final-function (<accumulator-finalizer> <accumulator>) ...)) - (let ((<body-binding> ... <body-binding-expr>) ...) - (let ((<user-binding> ... <user-binding-expr>) ...) - (match-let ((<parenthesised-pattern> <match-expr>)) - (if (and <when-expr> ...) + 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>) ...)) + ((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> ...))))))))) - - <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. + LOOP-BODY + (goof-loop ACCUMULATE ... LOOP-VAR-NEXT ...)) + (goof-loop ACCUMULATOR ... LOOP-VAR-NEXT ...))))))))) + + 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-variable>: <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 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. + WHEN-EXPR: the user supplied :when or :unless guard expression. - <user-break>: user-supplied :break guard. + 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. - 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.
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: