
:for clauses now have finalizers! This means (in-file "path") now works. This meant I had to do some restructuring of many nasty pieces of code, but I believe it works. cl-next was broken up into cl-next/acc and cl-next/for. Accumulators have to pass :acc to (cl-next/acc ...). I plan for :for clauses to do the same. I fixed tests.scm and README.md to reflect the de-racketification of the for loops.
111 lines
No EOL
4.3 KiB
Markdown
111 lines
No EOL
4.3 KiB
Markdown
# goof-loop - a scheme looping facility
|
|
|
|
WARNING: CURRENTLY PRE-ALPHA. The examples in this document are not consistent with the current direction I am pushing this (even though they _should_ work).
|
|
|
|
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:
|
|
|
|
```
|
|
(loop ((:for a (in 0 b))
|
|
(:for b (in 1 (+ a b)))
|
|
(count (up-from 0 (to 1000)))
|
|
(:acc acc (listing b)))
|
|
=> acc
|
|
(display b) (newline))
|
|
```
|
|
|
|
The above example will display and accumulate the 1000 first fibonacci numbers. Doing the same thing in racket requires you to manually handle all the state in fold-variables using for/fold. It is a simple example, but proves the usefulness of goof-loop.
|
|
|
|
Compared to foof-loop, some things are added. Apart from minor syntactic changes, subloops are supported. The best way is to show:
|
|
|
|
```
|
|
(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)
|
|
```
|
|
|
|
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.
|
|
|
|
Accumulators can be in any of the loop's stages:
|
|
|
|
```
|
|
(loop ((:for a (in-list '(1 2 3)))
|
|
(:acc aa (summing a))
|
|
:subloop
|
|
(:for b (up-from a (to (+ a 2))))
|
|
(:acc ab (listing b)))
|
|
=> (values aa ab))
|
|
;; => (values 6 (1 2 2 3 3 4))
|
|
```
|
|
|
|
## Differences from foof-loop
|
|
|
|
### syntactical
|
|
|
|
for-clauses are split into :for and :let 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 (:forvar (in init [step [stop]])) or (:acc var (folding init [step])) in case of accumulators.
|
|
|
|
### 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).
|
|
|
|
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)))
|
|
|
|
### similarities
|
|
|
|
You can of course still have a larger control of your loops:
|
|
|
|
```
|
|
(loop loopy-loop ((:for a (up-from 1 (to 11))))
|
|
=> '()
|
|
(if (odd? a)
|
|
(cons (* a (- a)) (loopy-loop))
|
|
(cons (* a a) (loopy-loop))))
|
|
|
|
;; => (-1 4 -9 16 -25 36 -49 64 -81 100)
|
|
```
|
|
|
|
Named updates also work.
|
|
|
|
```
|
|
;; Shamelessly stolen from Taylor Campbell's foof-loop documentation
|
|
(define (partition list predicate)
|
|
(loop continue ((:for element (in-list list))
|
|
(:acc satisfied (folding '()))
|
|
(:acc unsatisfied (folding '())))
|
|
=> (values (reverse satisfied)
|
|
(reverse unsatisfied))
|
|
(if (predicate element)
|
|
(continue (=> satisfied (cons element satisfied)))
|
|
(continue (=> unsatisfied (cons element unsatisfied))))))
|
|
|
|
(partition '(1 2 3 4 5) odd?)
|
|
;; => (values (1 3 5) (2 4))
|
|
```
|
|
|
|
|
|
## Todo
|
|
Tests and documentation.
|
|
|
|
Fix the inlining behavious of some of the :for iterators.
|
|
|
|
## 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.
|
|
|
|
## Licence
|
|
|
|
The same BSD-styled license Alex uses for chibi-loop. |