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.
This commit is contained in:
Linus 2021-05-18 19:57:02 +02:00
parent aa77fef2ad
commit 7a1137e579
3 changed files with 71 additions and 66 deletions

123
README.md
View file

@ -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.

View file

@ -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

View file

@ -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))))