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:
parent
aa77fef2ad
commit
7a1137e579
3 changed files with 71 additions and 66 deletions
123
README.md
123
README.md
|
@ -1,78 +1,32 @@
|
||||||
# goof-loop - a scheme looping facility
|
# 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:
|
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)))
|
(define lst '((1 2) dud (3 4) (5 6)))
|
||||||
(loop ((:for a (in-list lst))
|
(loop ((:for a (in-list lst))
|
||||||
(:when (pair? a))
|
(:when (pair? a))
|
||||||
(:for b (in-list a))
|
(:for b (in-list a))
|
||||||
(:acc acc (summing b)))
|
(:acc acc (summing b)))
|
||||||
=> acc)
|
=> 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
|
## 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
|
## 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.
|
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
|
## Features
|
||||||
|
|
||||||
### Lexical order of clauses
|
### Lexical order of clauses
|
||||||
|
@ -87,6 +41,18 @@ Due to clause reordering, positional updates are not supported. If you want to u
|
||||||
=> acc)
|
=> 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"
|
### Loop naming to make it "fold right"
|
||||||
|
|
||||||
You can of course still have a larger control of when to loop by naming your loop:
|
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)
|
;; => (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)
|
### :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))
|
;; => ((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:
|
The iterative fibonacci loop is weird to write using for/fold. goof fixes this:
|
||||||
``` scheme
|
``` 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)
|
;; => 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
|
### Simple forms
|
||||||
I also provide simplified forms for many common operations. Omitting :for is allowed, and :acc clauses are not allowed.
|
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)))
|
(let ((a (car cursor)) (succ (cdr cursor)))
|
||||||
(if (even? a)
|
(if (even? a)
|
||||||
(cons a (loopy-loop succ))
|
(cons a (loopy-loop succ))
|
||||||
(loopy-loop
|
(loopy-loop)))))
|
||||||
|
|
||||||
|
|
||||||
;; The code expansion of the partition procedure above produces
|
;; 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
|
## Todo
|
||||||
Tests!
|
Tests!
|
||||||
|
|
||||||
Finish documentation.
|
Finish documentation.
|
||||||
|
|
||||||
## foof, what a guy
|
## foof, what a guy
|
||||||
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
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.
|
||||||
The same BSD-styled license Alex uses for chibi-loop.
|
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
((loop . rest)
|
((loop . rest)
|
||||||
(%loop (loop . rest) . rest))))
|
(%loop (loop . rest) . rest))))
|
||||||
|
|
||||||
|
|
||||||
(define-syntax %loop
|
(define-syntax %loop
|
||||||
(syntax-rules (=>)
|
(syntax-rules (=>)
|
||||||
((%loop o () => expr body ...)
|
((%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)
|
((_ 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))
|
(cl orig name l a v c r f ff ((cur-ub ... (:break expr)) . ub-rest) (clauses ...) . body))
|
||||||
;; user final
|
;; 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 ((cur-user ...) . user-rest) ((:final expr) clauses ...) . body)
|
||||||
((_ orig name l a v c r f ff user ((: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))
|
||||||
(final :acc ((_) (expr)) cl-next/acc orig name l a v c r f ff user (clauses ...) . body))
|
|
||||||
|
|
||||||
;; User do - sideffecting stuff.
|
;; User do - sideffecting stuff.
|
||||||
((_ orig name l a v c r f ff ((cur-uw ...) . uw-rest) ((:do expr ...) clauses ...) . body)
|
((_ orig name l a v c r f ff ((cur-uw ...) . uw-rest) ((:do expr ...) clauses ...) . body)
|
||||||
|
@ -196,7 +196,7 @@
|
||||||
checks
|
checks
|
||||||
((refs ... new-refs ...))
|
((refs ... new-refs ...))
|
||||||
(finals ... new-finals ...)
|
(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!
|
;; We have ONE subloop!
|
||||||
((_ (new-lets ...) ((accvar accinit accupdate) ...) (new-checks ...) (new-refs ...) (new-finals ...)
|
((_ (new-lets ...) ((accvar accinit accupdate) ...) (new-checks ...) (new-refs ...) (new-finals ...)
|
||||||
orig name
|
orig name
|
||||||
|
|
|
@ -512,13 +512,13 @@
|
||||||
;; this is an internal "accumulator". It is used for final tests
|
;; this is an internal "accumulator". It is used for final tests
|
||||||
;; :final in goof differs from in racket. It is lexical, meaning it
|
;; :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
|
;; is tested where it is placed in the clauses, and any subloop is
|
||||||
;; executed completely.
|
;; executed until exhaustion.
|
||||||
(define-syntax final
|
(define-syntax final
|
||||||
(syntax-rules (:acc)
|
(syntax-rules (:acc)
|
||||||
((_ :acc ((var) (test)) n . rest)
|
((_ :acc ((var) (test)) n . rest)
|
||||||
(n ()
|
(n ()
|
||||||
((final #f test))
|
((var #f test))
|
||||||
(final)
|
()
|
||||||
()
|
()
|
||||||
()
|
()
|
||||||
. rest))))
|
. rest))))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue