From 7a1137e57983969d1bdaa85212e2e9d248d37bc4 Mon Sep 17 00:00:00 2001 From: Linus Date: Tue, 18 May 2021 19:57:02 +0200 Subject: [PATCH] Changed accumulator test/bind order to bind/test This means vectoring exits directly when the index loop variable = :length. It also means :final has to change. --- README.md | 123 +++++++++++++++++++++++---------------------- goof-impl.scm | 8 +-- goof/iterators.scm | 6 +-- 3 files changed, 71 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 1a25c14..e11aa5b 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,32 @@ # goof-loop - a scheme looping facility -goof-loops aims to be an amalgamation of the racket for loops and Alex Shinn's (chibi-loop). We are many that found racket's for loops a breeze of fresh air, but in the end their most general forms (for/fold and for/foldr) are kinda odd to work with. If you choose not to use those general for loops, you cannot express arbitrary transformations, like say a fibonacci sequence, since for clauses cannot reference eachother. goof-loop tries to fix this. +goof-loops aims to be an amalgamation of the racket for loops and Alex Shinn's (chibi-loop). We are many that found racket's for loops a breeze of fresh air, but in the end their most general forms (for/fold and for/foldr) are kinda odd to work with. If you choose not to use those general for loops, you cannot express arbitrary transformations, like say a fibonacci sequence, since for clauses cannot reference eachother. goof-loop tries to fix this. Compared to foof-loop, some things are added. Apart from minor syntactic changes, subloops are supported. The best way is to show: -``` +``` scheme (define lst '((1 2) dud (3 4) (5 6))) (loop ((:for a (in-list lst)) (:when (pair? a)) (:for b (in-list a)) (:acc acc (summing b))) => acc) +;; => 21 ``` -This will sum all the sublists of lst and produce the result 21. Any :when, :unless, :break, :final, or :subloop clause will break out a subloop if any subsequent for clauses are found. + Any :when, :unless, :break, :final, :bind, :do or :subloop clause will break out a subloop if any subsequent for clauses are found. ## Beta warning -This is beta quality software, and some minor details are likely to change. I have gotten most kinks worked out though. +This is beta quality software, and some minor details are likely to change. I have gotten most kinks worked out though, but if I ever figure out how to include branching, the body-part of the macro will become a :body clause. ## Documentation -The current WIP documentation can be found here: https://bjoli.srht.site/doc.html +The current WIP documentation can be found here: https://bjoli.srht.site/doc.html (WARNING: for 0.1, not master) 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 - -### lexical - -foof-loop has a lot of code movement going on, and it can be hard to understand exactly where things end up. goof employs a more strict lexical hierarchy. The following is not possible in (chibi loop): -d into only after the above clauses have been evaluated. - -### syntactical - -for-clauses are split into :for and :acc clauses. This is because the addition of subloops means we have to treat accumulators differently. - -while and until are removed in favour of :break. - -:when and :unless are added to better control when the loop body is executed (and accumulators accumulated) - -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). - -Due to clause reordering, positional updates are not supported. If you want to update your loop vars, do so using named update (see below). - -### changes - - (with var [init [step [guard]]]) => (:for var (in init [step [stop-expr]])). - - guard was a procedure, but now it is an expression. - - (with var 10 (- var 1) negative?) => (:for var (in 10 (- var 10) (negative? var))) - ## Features ### Lexical order of clauses @@ -87,6 +41,18 @@ Due to clause reordering, positional updates are not supported. If you want to u => acc) ``` +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. + +``` scheme +(loop ((:for a (in-list '(1 2 3))) + (:acc vec (vectoring a (:length 2))) + ;; implicit :break (= vec-index 2) + (:acc sum (summing a))) + => (values vec sum)) +;; => #(1 2) 1 + +``` + ### Loop naming to make it "fold right" You can of course still have a larger control of when to loop by naming your loop: @@ -131,6 +97,26 @@ The iterator protocol allows exposing the loop variables ;; => (1 : 2 : 3) ``` +In the above example, pair is bound to the pair where elt is the car. + +### Higher order loop protocol + +goof supports a higher order looping protocol, based on srfi-158 generators: + +``` scheme + +(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. ### :final is context sensitive (compared to Racket's #:final) @@ -145,9 +131,9 @@ The iterator protocol allows exposing the loop variables ;; => ((1 . a) (1 . b) (2 . a) (2 . b)) ``` -The racket counterpart would result in ((1 . a) (1 . b) (2 . a)) +The racket counterpart would result in ((1 . a) (1 . b) (2 . a)). This comes at :final clauses being less efficient than racket's #:final, but not by much. -### for-clauses can refer to eachother +### :for-clauses can refer to eachother The iterative fibonacci loop is weird to write using for/fold. goof fixes this: ``` scheme @@ -171,7 +157,18 @@ The iterative fibonacci loop is weird to write using for/fold. goof fixes this: ;; => 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. + +``` scheme +(loop ((:for (key . val) (in-list '((a . 1) (b . 2) c . 3))) + (:acc sum (summing val))) + => sum) +;; => 6 + +This also works with :bind clauses. +``` ### Simple forms I also provide simplified forms for many common operations. Omitting :for is allowed, and :acc clauses are not allowed. @@ -245,7 +242,7 @@ $5 = (let loopy-loop ((cursor (read))) (let ((a (car cursor)) (succ (cdr cursor))) (if (even? a) (cons a (loopy-loop succ)) - (loopy-loop + (loopy-loop))))) ;; The code expansion of the partition procedure above produces @@ -265,15 +262,23 @@ $5 = (let loopy-loop ((cursor (read))) ``` + +## Differences from foof-loop + +This used to be a pretty vast collection of examples. goof-loof is now different enough from foof loop that you can't expect to carry your foof-loop skills over to goof-loop. There are however two notable regressions. + +### 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). + +Due to clause reordering, positional updates are not supported. If you want to update your loop vars, do so using named update (see below). + ## Todo Tests! Finish documentation. ## foof, what a guy - -I have previously expressed some admiration for Alex 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. +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. ## Licence - -The same BSD-styled license Alex uses for chibi-loop. +goof started off by copying the iterator protocol of (chibi loop), and things sort of went downhill from there. Despite this, there is still quite a lot of code (especially in iterators.scm) that I didn't write myself. I only made it ugly. Thus goof is licensed under the same BSD-styled license Alex uses for chibi-loop. diff --git a/goof-impl.scm b/goof-impl.scm index ba68b59..50abfc6 100644 --- a/goof-impl.scm +++ b/goof-impl.scm @@ -54,6 +54,7 @@ ((loop . rest) (%loop (loop . rest) . rest)))) + (define-syntax %loop (syntax-rules (=>) ((%loop o () => expr body ...) @@ -142,9 +143,8 @@ ((_ orig name l a v c r f ff ((cur-ub ...) . ub-rest) ((:break expr) clauses ...) . body) (cl orig name l a v c r f ff ((cur-ub ... (:break expr)) . ub-rest) (clauses ...) . body)) ;; user final - ;; This pushes a #t to the user when expression, thus forcing a subloop if a for-clause is found afterwards. - ((_ orig name l a v c r f ff user ((:final expr) clauses ...) . body) - (final :acc ((_) (expr)) cl-next/acc orig name l a v c r f ff user (clauses ...) . body)) + ((_ orig name l a v c r f ff ((cur-user ...) . user-rest) ((:final expr) clauses ...) . body) + (final :acc ((fin) (expr)) cl-next/acc orig name l a v c r f ff ((cur-user ... (:break fin)) . user-rest) (clauses ...) . body)) ;; User do - sideffecting stuff. ((_ orig name l a v c r f ff ((cur-uw ...) . uw-rest) ((:do expr ...) clauses ...) . body) @@ -196,7 +196,7 @@ checks ((refs ... new-refs ...)) (finals ... new-finals ...) - ff ((cur-ub ... (:break new-checks) ... (:bind (accvar accupdate) ...)) . ub-rest) clauses . body)) + ff ((cur-ub ... (:bind (accvar accupdate) ...) (:break new-checks) ... ) . ub-rest) clauses . body)) ;; We have ONE subloop! ((_ (new-lets ...) ((accvar accinit accupdate) ...) (new-checks ...) (new-refs ...) (new-finals ...) orig name diff --git a/goof/iterators.scm b/goof/iterators.scm index c5f982d..f332b28 100644 --- a/goof/iterators.scm +++ b/goof/iterators.scm @@ -512,13 +512,13 @@ ;; this is an internal "accumulator". It is used for final tests ;; :final in goof differs from in racket. It is lexical, meaning it ;; is tested where it is placed in the clauses, and any subloop is -;; executed completely. +;; executed until exhaustion. (define-syntax final (syntax-rules (:acc) ((_ :acc ((var) (test)) n . rest) (n () - ((final #f test)) - (final) + ((var #f test)) + () () () . rest))))