Made => implicit if we have accumulators

If there are accumulators, their final values will be returned if no
final-expr is given.

(loop ((:for a (up-from 1 11))
       (:acc lst (listing (* a a))))
  => lst)

becomes simply:

(loop ((:for a (up-from 1 11))
       (:acc lst (listing (* a a)))))
This commit is contained in:
Linus 2021-05-18 20:39:37 +02:00
parent 7a1137e579
commit 93134a1b21
2 changed files with 36 additions and 29 deletions

View file

@ -9,8 +9,7 @@ Compared to foof-loop, some things are added. Apart from minor syntactic changes
(loop ((:for a (in-list lst))
(:when (pair? a))
(:for b (in-list a))
(:acc acc (summing b)))
=> acc)
(:acc acc (summing b))))
;; => 21
```
@ -37,8 +36,7 @@ It is written in a weird markdown/xml chimaera. You can find it in documentation
(:when (test? b))
(:bind (c (expensive-operation2 b)))
(:when test2? c)
(:acc acc (listing c))))
=> acc)
(:acc acc (listing c)))))
```
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.
@ -47,10 +45,22 @@ There is one caveat to this: some accumulating clauses (currently only vectoring
(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))
(:acc sum (summing a))))
;; => #(1 2) 1
```
### Ability to be explicit about returned values
If you have no "final expression", denoted by => expr, one is added that returns all final values of the :acc clauses in the loop. If none is present, the return value is unspecified.
``` scheme
(loop ((:for a (up-from 1 5))
(:acc sum (summing a))))
;; => 10
(loop ((:for a (up-from 1 5))
(:acc sum (summing a)))
=> (- sum))
;; => -10
```
### Loop naming to make it "fold right"
@ -66,6 +76,8 @@ 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)
```
Replace that cons with stream-cons and you have a lazy construct.
### Named updates
``` scheme
@ -81,7 +93,7 @@ You can of course still have a larger control of when to loop by naming your loo
(continue (=> unsatisfied (cons element unsatisfied))))))
(partition '(1 2 3 4 5) odd?)
;; => (values (1 3 5) (2 4))
;; => (1 3 5) (2 4)
```
### Exposing loop variables
@ -123,15 +135,14 @@ In the above example true? never ends, but restarts every time the list is exhau
``` scheme
(loop ((:for elt (in-list '( 1 2 3)))
:final (= elt 2)
(:final (= elt 2))
(:for ab (in-list '(a b)))
(:acc acc (listing (cons elt ab)))
=> acc))
(:acc acc (listing (cons elt ab))))
;; => ((1 . a) (1 . b) (2 . a) (2 . b))
```
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.
The racket counterpart would result in ((1 . a) (1 . b) (2 . a)). This comes at :final clauses being marginally less efficient than racket's #:final.
### :for-clauses can refer to eachother
@ -139,9 +150,8 @@ 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)))
(:for count (up-from 0 (:to 100)))
(:acc acc (listing b)))
=> acc
(display b) (newline))
```
### Accumulators and arbitrary code can be placed in subloops
@ -152,8 +162,7 @@ The iterative fibonacci loop is weird to write using for/fold. goof fixes this:
(:do (display "Entering subloop!") (newline))
:subloop
(:for b (up-from a (:to (+ a 2))))
(:acc ab (listing b)))
=> (values aa ab))
(:acc ab (listing b))))
;; => 6 (1 2 2 3 3 4)
```
@ -163,8 +172,7 @@ For clauses which bind "body bindings" (every one except (in ...)) can use patte
``` scheme
(loop ((:for (key . val) (in-list '((a . 1) (b . 2) c . 3)))
(:acc sum (summing val)))
=> sum)
(:acc sum (summing val))))
;; => 6
This also works with :bind clauses.
@ -183,12 +191,12 @@ I also provide simplified forms for many common operations. Omitting :for is all
(loop/product ((a (in-list '(2 3 4))))
a)
;; => 24
;; => 24
(loop/first ((a (in-list '(a b c 3 4 d))) (:when (integer? a)))
(display a)
a)
;; => displays 3 and returns 3.
;; => displays 3 and returns 3.
(loop/last ((a (in-list '(a b c d e f))) (:break (eq? a 'e)))
a)
@ -215,8 +223,7 @@ Speed is good. Despite the rather involved expansion you can see in the document
``` scheme
> ,opt (loop ((:for a (in-list '(1 2 3 4)))
(:when (even? a))
(:acc acc (listing a)))
=> acc)
(:acc acc (listing a))))
$1 = (let loopy-loop ((cursor-1 '()) (cursor '(1 2 3 4)))
(if (pair? cursor)
(let ((a (car cursor)) (succ (cdr cursor)))

View file

@ -56,14 +56,10 @@
(define-syntax %loop
(syntax-rules (=>)
((%loop o () => expr body ...)
(%loop o ((:for ensure-once (up-from 0 1))) => expr body ...))
(syntax-rules ()
((%loop o () body ...)
(%loop o ((:for ensure-once (up-from 0 1))) body ...))
((%loop o name () => expr body ...)
(%loop o name ((:for ensure-once (up-from 0 1))) => expr body ...))
((%loop o name () body ...)
((%loop o name () body ...)
(%loop o name ((:for ensure-once (up-from 0 1))) body ...))
((%loop o (clauses ...) body ...)
(ensure-for-clause #f () (clauses ...) o
@ -125,8 +121,12 @@
(syntax-rules (=> :for :acc :when :unless :break :final :do :bind :subloop)
((_ orig name l a v c r f ff user () => expr . body)
(emit orig name l a v c r f ff user expr . body))
((_ orig name l a v c r f ff user () . body)
(emit orig name l a v c r f ff user (if #f #f) . body))
((_ orig name l a v c r () ff user () . body)
(emit orig name l a v c r () ff user (if #f #f) . body))
;; If we have no final-expr, but we have final bindings, we return those.
((_ orig name l a v c r ((final-binding expr) ...) ff user () . body)
(emit orig name l a v c r ((final-binding expr) ...) ff user (values final-binding ...) . body))
;; user bindings
((_ orig name l a v c r f ff ((cur-ul ...) . ul-rest) ((:bind (id id* ... expr) ...) clauses ...) . body)