Big change: lexical scoping

This introduces lexical scoping of for clauses. See README.md
This commit is contained in:
Linus 2021-05-18 18:12:01 +02:00
parent 769553832b
commit 2c323be362
5 changed files with 132 additions and 63 deletions

107
README.md
View file

@ -1,17 +1,6 @@
# 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:
```
(loop ((:for a (in 0 b))
(:for b (in 1 (+ a b)))
(:for 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.
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:
@ -26,18 +15,6 @@ Compared to foof-loop, some things are added. Apart from minor syntactic changes
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))
```
## Beta warning
This is beta quality software, and some minor details are likely to change. I have gotten most kinks worked out though.
@ -51,6 +28,11 @@ It is written in a weird markdown/xml chimaera. You can find it in documentation
## 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.
@ -90,8 +72,20 @@ Due to clause reordering, positional updates are not supported. If you want to u
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
### similarities
### Lexical order of clauses
(loop ((:for a (in-list 1 2 3)
(:bind b (expensive-operation1 a))
(:when (test? b))
(:bind c (expensive-operation2 b))
(:when test2? c)
(:acc acc (listing c))))
=> acc)
### Loop naming to make it "fold right"
You can of course still have a larger control of when to loop by naming your loop:
@ -105,7 +99,7 @@ You can of course still have a larger control of when to loop by naming your loo
;; => (-1 4 -9 16 -25 36 -49 64 -81 100)
```
Named updates also work.
### Named updates
```
;; Shamelessly stolen from Taylor Campbell's foof-loop documentation
@ -123,6 +117,61 @@ Named updates also work.
;; => (values (1 3 5) (2 4))
```
### Exposing loop variables
The iterator protocol allows exposing the loop variables
```
(loop name ((:for elt pair (in-list '(1 2 3))))
=> '()
(if (null? (cdr pair))
(list elt)
(cons* elt ': (name))))
;; => (1 : 2 : 3)
```
### :final is context sensitive (compared to Racket's #:final)
``` scheme
(loop ((:for elt (in-list '( 1 2 3)))
:final (= elt 2)
(:for ab (in-list '(a b)))
(:acc acc (listing (cons elt ab)))
=> acc))
;; => ((1 . a) (1 . b) (2 . a) (2 . b))
```
The racket counterpart would result in ((1 . a) (1 . b) (2 . a))
### for-clauses can refer to eachother
The iterative fibonacci loop is weird to write using for/fold. goof fixes this:
``` scheme
(loop ((:for a (in 0 b))
(:for b (in 1 (+ a b)))
(:for count (up-from 0 (to 100)))
(:acc acc (listing b)))
=> acc
(display b) (newline))
```
### Accumulators and arbitrary code can be placed in subloops
``` scheme
(loop ((:for a (in-list '(1 2 3)))
(:acc aa (summing a))
(:do (display "Entering subloop!") (newline))
:subloop
(:for b (up-from a (:to (+ a 2))))
(:acc ab (listing b)))
=> (values aa ab))
;; => 6 (1 2 2 3 3 4)
```
### Simple forms
I also provide simplified forms for many common operations. Omitting :for is allowed, and :acc clauses are not allowed.
@ -161,9 +210,9 @@ I also provide simplified forms for many common operations. Omitting :for is all
```
### Speed
## Speed
Speed is good. Despite the rather involved expansion you can see in the documentation, due to dead-code elimination, the actual expansion shows some good code:
Speed is good. Despite the rather involved expansion you can see in the documentation, due to inlining and dead-code elimination, the actual expansion shows some good code:
```
> ,opt (loop ((:for a (in-list '(1 2 3 4)))
@ -220,8 +269,6 @@ Tests!
Finish documentation.
add generator support for all provided 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.