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)) (loop ((:for a (in-list lst))
(:when (pair? a)) (:when (pair? a))
(:for b (in-list a)) (:for b (in-list a))
(:acc acc (summing b))) (:acc acc (summing b))))
=> acc)
;; => 21 ;; => 21
``` ```
@ -37,8 +36,7 @@ It is written in a weird markdown/xml chimaera. You can find it in documentation
(:when (test? b)) (:when (test? b))
(:bind (c (expensive-operation2 b))) (:bind (c (expensive-operation2 b)))
(:when test2? c) (:when test2? c)
(:acc acc (listing c)))) (:acc acc (listing c)))))
=> 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. 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))) (loop ((:for a (in-list '(1 2 3)))
(:acc vec (vectoring a (:length 2))) (:acc vec (vectoring a (:length 2)))
;; implicit :break (= vec-index 2) ;; implicit :break (= vec-index 2)
(:acc sum (summing a))) (:acc sum (summing a))))
=> (values vec sum))
;; => #(1 2) 1 ;; => #(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" ### 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) ;; => (-1 4 -9 16 -25 36 -49 64 -81 100)
``` ```
Replace that cons with stream-cons and you have a lazy construct.
### Named updates ### Named updates
``` scheme ``` 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)))))) (continue (=> unsatisfied (cons element unsatisfied))))))
(partition '(1 2 3 4 5) odd?) (partition '(1 2 3 4 5) odd?)
;; => (values (1 3 5) (2 4)) ;; => (1 3 5) (2 4)
``` ```
### Exposing loop variables ### Exposing loop variables
@ -123,15 +135,14 @@ In the above example true? never ends, but restarts every time the list is exhau
``` scheme ``` scheme
(loop ((:for elt (in-list '( 1 2 3))) (loop ((:for elt (in-list '( 1 2 3)))
:final (= elt 2) (:final (= elt 2))
(:for ab (in-list '(a b))) (:for ab (in-list '(a b)))
(:acc acc (listing (cons elt ab))) (:acc acc (listing (cons elt ab))))
=> acc))
;; => ((1 . a) (1 . b) (2 . a) (2 . b)) ;; => ((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 ### :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 ``` scheme
(loop ((:for a (in 0 b)) (loop ((:for a (in 0 b))
(:for b (in 1 (+ a 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 acc (listing b)))
=> acc
(display b) (newline)) (display b) (newline))
``` ```
### Accumulators and arbitrary code can be placed in subloops ### 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)) (:do (display "Entering subloop!") (newline))
:subloop :subloop
(:for b (up-from a (:to (+ a 2)))) (:for b (up-from a (:to (+ a 2))))
(:acc ab (listing b))) (:acc ab (listing b))))
=> (values aa ab))
;; => 6 (1 2 2 3 3 4) ;; => 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 ``` scheme
(loop ((:for (key . val) (in-list '((a . 1) (b . 2) c . 3))) (loop ((:for (key . val) (in-list '((a . 1) (b . 2) c . 3)))
(:acc sum (summing val))) (:acc sum (summing val))))
=> sum)
;; => 6 ;; => 6
This also works with :bind clauses. 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)))) (loop/product ((a (in-list '(2 3 4))))
a) a)
;; => 24 ;; => 24
(loop/first ((a (in-list '(a b c 3 4 d))) (:when (integer? a))) (loop/first ((a (in-list '(a b c 3 4 d))) (:when (integer? a)))
(display a) (display a)
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))) (loop/last ((a (in-list '(a b c d e f))) (:break (eq? a 'e)))
a) a)
@ -215,8 +223,7 @@ Speed is good. Despite the rather involved expansion you can see in the document
``` scheme ``` scheme
> ,opt (loop ((:for a (in-list '(1 2 3 4))) > ,opt (loop ((:for a (in-list '(1 2 3 4)))
(:when (even? a)) (:when (even? a))
(:acc acc (listing a))) (:acc acc (listing a))))
=> acc)
$1 = (let loopy-loop ((cursor-1 '()) (cursor '(1 2 3 4))) $1 = (let loopy-loop ((cursor-1 '()) (cursor '(1 2 3 4)))
(if (pair? cursor) (if (pair? cursor)
(let ((a (car cursor)) (succ (cdr cursor))) (let ((a (car cursor)) (succ (cdr cursor)))

View file

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