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-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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue