
: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.
4.3 KiB
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.