Changes for the better

* goof-impl.scm (loop/first loop/last): add ability to specify a
:default value. Added auxiliary syntax :default.
 * goof/iterators.scm (accumulating hash(q|v)ing): changed auxiliary
keyword from initial -> :initial.
 * goof.scm: export extra keywords
 * doc.html
 * doc.xml : document changes. fix bugs.
This commit is contained in:
Linus 2021-08-17 21:36:13 +02:00
parent 1de0a624f5
commit 832c414260
6 changed files with 77 additions and 61 deletions

View file

@ -67,7 +67,7 @@
<pre class="code-example">
(loop ((:for a (in-list '(1 2 3)))
:subloop
(:for b (up-from 0 (to a)))
(:for b (up-from 0 (:to a)))
(:acc acc (listing (cons a b)))))
;; =&gt; ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
@ -75,7 +75,7 @@
<pre class="code-example">
(loop ((:for a (in-list '(1 2 3)))
(:break (= 3 a))
(:for b (up-from 0 (to a)))
(:for b (up-from 0 (:to a)))
(:acc acc (listing (cons a b)))))
;; =&gt; ((1 . 0) (2 . 0) (2 . 1))
@ -83,10 +83,10 @@
<pre class="code-example">
(loop ((:for a (in-list '(1 2 3 4)))
(:final (= 3 a))
(:for b (up-from 0 (to a)))
(:for b (up-from 0 (:to a)))
(:acc acc (listing (cons a b)))))
;; =&gt; ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1))
;; =&gt; ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
</pre><p>The :final clause is actually equivalent to something alike the following:</p>
<pre class="code-example">
(loop ((:for a (in-list '(1 2 3 4)))
@ -117,13 +117,13 @@
<p>Some loop clauses have the option of exposing their loop variable(s). The option to do so is documented under the documentation for each clause.</p>
</div><div id="Simple forms"><h3>Simple forms</h3><p>The pure <code>loop</code> macro is quite a big hammer for most tasks. Often we want to do simple things, like collect elements into a list or a vector, which means the extra housekeeping of separating accumulators and for clauses are too much heavy lifting. goof-loop provides several simpler forms that can be used in those cases. In these simpler forms :acc is disallowed, and everything not identified as anything else is assumed to be a :for clause. The loop below accumulates the 100 first fibonacci numbers into a list.</p>
<pre class="code-example">
(loop/list ((count (up-from 0 (to 100)))
(loop/list ((count (up-from 0 (:to 100)))
(a (in 0 b))
(b (in 1 (+ a b))))
b)
</pre><p>The simple forms provided by goof-loop are the following:</p>
<dl><dt><a id="loop/first"><b>Scheme syntax: </b>loop/first</a><dd><code>(loop/first (clauses ...) body ...)</code><br /><p>If any body is ever evaluated, stop and return the value of that evaluation. If no body is ever evaluated the return value is unspecified.</p>
</dd></dt><dt><a id="loop/last"><b>Scheme syntax: </b>loop/last</a><dd><code>(loop/last (clauses ...) body ...)</code><br /><p>Returns the result of the last body to be evaluated. If no body is evaluated the return value is unspecified.</p>
<dl><dt><a id="loop/first"><b>Scheme syntax: </b>loop/first</a><dd><code>(loop/first [:default #f] (clauses ...) body ...)</code><br /><p>If any body is ever evaluated, stop and return the value of that evaluation. If no body is evaluated it returns the value specified by <code>:default</code>, which defaults to #f.</p>
</dd></dt><dt><a id="loop/last"><b>Scheme syntax: </b>loop/last</a><dd><code>(loop/last [:default #f] (clauses ...) body ...)</code><br /><p>Returns the result of the last body to be evaluated. If no body is evaluated it returns the value specified by <code>:default</code>, which defaults to #f.</p>
</dd></dt><dt><a id="loop/list"><b>Scheme syntax: </b>loop/list</a><dd><code>(loop/list (clauses ...) body ...)</code><br /><p>Iterates over <code>clauses</code> and builds a list of the result of every evaluation of body. The order of the list is the same as the order body was evaluated in. The result of the first evaluation of body is the first element of the resulting list.</p>
<p>The list returned is the same even when used with multi-shot continuations.</p>
<p>If no body is evaluated, the result is the empty list.</p>
@ -167,25 +167,25 @@
</dd></dt></dl></div><div id=":acc-clauses"><h3>:acc-clauses</h3><p>Accumulating clauses differ from :for-clauses in 2 significant ways. They have a final value available in the <code>final-expr</code>, and they keep their state throughout the loop. In the case of a loop with one subloop, the :for-clauses reset their state every time the subloop is entered. :acc-clauses will always keep their state.</p>
<p>Another small thing is that for some :acc-clauses, the <code>binding</code> may sometimes only be visible to the user in the <code>final-expr</code>, but like :for-clauses they sometimes offer the programmer to name the loop variables.</p>
<p>Many accumulating clauses support an <code>if</code> form. If such a clause is given, accumulation will only happen if the guard clause returns true.</p>
<dl><dt><a id="listing"><b>Scheme syntax: </b>listing</a><dd><code>(:acc binding (listing [(initial init)] expr [if guard]))</code><br /><p>Accumulates <code>expr</code> into a list. ´binding<code>is only accesible in the final-expression. The list is in the same order as the loop bodies were evaluated. If</code>initial<code>is given that will be used as the tail of the accumulated results. It defaults to</code>()`.</p>
</dd></dt><dt><a id="listing-reverse"><b>Scheme syntax: </b>listing-reverse</a><dd><code>(:acc binding (listing-reverse [(initial init)] expr [if guard]))</code><br /><p>The same as <code>listing</code> but the resulting list in in reverse order. If the order of the resulting list does not matter, this will be faster than the regular listing as it will not preform any reverse at the end.</p>
</dd></dt><dt><a id="appending"><b>Scheme syntax: </b>appending</a><dd><code>(:acc binding (appending [(initial init)] expr [if guard]))</code><br /><p><code>expr</code> evaluates to a list that is then appended to the accumulated result.</p>
<dl><dt><a id="listing"><b>Scheme syntax: </b>listing</a><dd><code>(:acc binding (listing [(:initial init)] expr [if guard]))</code><br /><p>Accumulates <code>expr</code> into a list. ´binding<code>is only accesible in the final-expression. The list is in the same order as the loop bodies were evaluated. If</code>initial<code>is given that will be used as the tail of the accumulated results. It defaults to</code>()`.</p>
</dd></dt><dt><a id="listing-reverse"><b>Scheme syntax: </b>listing-reverse</a><dd><code>(:acc binding (listing-reverse [(:initial init)] expr [if guard]))</code><br /><p>The same as <code>listing</code> but the resulting list in in reverse order. If the order of the resulting list does not matter, this will be faster than the regular listing as it will not preform any reverse at the end.</p>
</dd></dt><dt><a id="appending"><b>Scheme syntax: </b>appending</a><dd><code>(:acc binding (appending [(:initial init)] expr [if guard]))</code><br /><p><code>expr</code> evaluates to a list that is then appended to the accumulated result.</p>
<pre class="code-example">
(loop ((:for elt (in-list '((1 2) (3 4))))
(:acc acc (appending (initial '(0)) elt)))
(:acc acc (appending (:initial '(0)) elt)))
=&gt; acc)
;; =&gt; (0 1 2 3 4)
</pre></dd></dt><dt><a id="appending-reverse"><b>Scheme syntax: </b>appending-reverse</a><dd><code>(:acc binding (appending-reverse [(initial init)] expr [if guard]))</code><br /><p><code>expr</code> evaluates to a list that is then consed element by element onto the already accumulated results. The default initial value is <code>'()</code>.</p>
</pre></dd></dt><dt><a id="appending-reverse"><b>Scheme syntax: </b>appending-reverse</a><dd><code>(:acc binding (appending-reverse [(:initial init)] expr [if guard]))</code><br /><p><code>expr</code> evaluates to a list that is then consed element by element onto the already accumulated results. The default initial value is <code>'()</code>.</p>
<pre class="code-example">
(loop ((:for elt (in-list '((1 2) (3 4))))
(:acc acc (appending-reverse (initial '(0)) elt)))
(:acc acc (appending-reverse (:initial '(0)) elt)))
=&gt; acc)
;; =&gt; (4 3 2 1 0)
</pre></dd></dt><dt><a id="summing"><b>Scheme syntax: </b>summing</a><dd><code>(:acc binding (summing [(initial init)] expr [(if guard)]))</code><br /><p>Adds the result of <code>expr</code> together using <code>+</code>. The default initial value is 0.</p>
</dd></dt><dt><a id="multiplying"><b>Scheme syntax: </b>multiplying</a><dd><code>(:acc binding (multiplying [(initial init)] expr [(if guard)]))</code><br /><p>Multiplies the result of <code>expr</code> using <code>*</code>. The default initial value is 1.</p>
</dd></dt><dt><a id="hashing"><b>Scheme syntax: </b>hashing</a><dd><code>(:acc binding (hashing [(initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key =&gt; value)</code> to the hashtable <code>binding</code> using equal?-hashing. The initial hash table is an empty hash-table. <code>binding</code> is bound to the hash table throughout the loop, and its content can be mutated in the loop body.</p>
</dd></dt><dt><a id="hashving"><b>Scheme syntax: </b>hashving</a><dd><code>(:acc binding (hashving [(initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key =&gt; value)</code> to the hashtable <code>binding</code> using eqv?-hashing. The initial hash table is an empty hash-table. <code>binding</code> is bound to the hash table throughout the loop, and its content can be mutated in the loop body.</p>
</dd></dt><dt><a id="hashqing"><b>Scheme syntax: </b>hashqing</a><dd><code>(:acc binding (hashqing [(initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key =&gt; value)</code> to a hashtable using eq?-hashing. The initial hash table is an empty hash-table.<code>binding</code> is bound to the hash table throughout the loop, and its can be mutated in the loop body.</p>
</pre></dd></dt><dt><a id="summing"><b>Scheme syntax: </b>summing</a><dd><code>(:acc binding (summing [(:initial init)] expr [(if guard)]))</code><br /><p>Adds the result of <code>expr</code> together using <code>+</code>. The default initial value is 0.</p>
</dd></dt><dt><a id="multiplying"><b>Scheme syntax: </b>multiplying</a><dd><code>(:acc binding (multiplying [(:initial init)] expr [(if guard)]))</code><br /><p>Multiplies the result of <code>expr</code> using <code>*</code>. The default initial value is 1.</p>
</dd></dt><dt><a id="hashing"><b>Scheme syntax: </b>hashing</a><dd><code>(:acc binding (hashing [(:initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key =&gt; value)</code> to the hashtable <code>binding</code> using equal?-hashing. The initial hash table is an empty hash-table. <code>binding</code> is bound to the hash table throughout the loop, and its content can be mutated in the loop body.</p>
</dd></dt><dt><a id="hashving"><b>Scheme syntax: </b>hashving</a><dd><code>(:acc binding (hashving [(:initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key =&gt; value)</code> to the hashtable <code>binding</code> using eqv?-hashing. The initial hash table is an empty hash-table. <code>binding</code> is bound to the hash table throughout the loop, and its content can be mutated in the loop body.</p>
</dd></dt><dt><a id="hashqing"><b>Scheme syntax: </b>hashqing</a><dd><code>(:acc binding (hashqing [(:initial init)] key value [(if guard)]))</code><br /><p>Adds the mapping <code>(key =&gt; value)</code> to a hashtable using eq?-hashing. The initial hash table is an empty hash-table.<code>binding</code> is bound to the hash table throughout the loop, and its can be mutated in the loop body.</p>
</dd></dt><dt><a id="vectoring"><b>Scheme syntax: </b>vectoring</a><dd><code>(:acc binding [index] (vectoring expr [(:length len) [(:fill fill)]]))</code><br /><p>Accumulates the result of <code>expr</code> into a vector. If <code>len</code> and <code>fill</code> is given the vector will be at most <code>len</code> elements long and any unfilled indexes will contain the element <code>fill</code>. The loop will exit when <code>len</code> elements have been accumulated.</p>
<p>If <code>length</code> is not given, the vector will be expanded as required.</p>
<p>A vectoring clause adds an implicit <code>(:break (= index len))</code> after the vectoring clause. Once the last element of the vector is filled, the loop will stop and no subsequent clauses or body will be executed.</p>

View file

@ -106,7 +106,7 @@
<example>
(loop ((:for a (in-list '(1 2 3)))
:subloop
(:for b (up-from 0 (to a)))
(:for b (up-from 0 (:to a)))
(:acc acc (listing (cons a b)))))
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
@ -117,7 +117,7 @@
<example>
(loop ((:for a (in-list '(1 2 3)))
(:break (= 3 a))
(:for b (up-from 0 (to a)))
(:for b (up-from 0 (:to a)))
(:acc acc (listing (cons a b)))))
;; => ((1 . 0) (2 . 0) (2 . 1))
@ -128,10 +128,10 @@
<example>
(loop ((:for a (in-list '(1 2 3 4)))
(:final (= 3 a))
(:for b (up-from 0 (to a)))
(:for b (up-from 0 (:to a)))
(:acc acc (listing (cons a b)))))
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1))
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
</example>
The :final clause is actually equivalent to something alike the following:
@ -182,7 +182,7 @@
The pure `loop` macro is quite a big hammer for most tasks. Often we want to do simple things, like collect elements into a list or a vector, which means the extra housekeeping of separating accumulators and for clauses are too much heavy lifting. goof-loop provides several simpler forms that can be used in those cases. In these simpler forms :acc is disallowed, and everything not identified as anything else is assumed to be a :for clause. The loop below accumulates the 100 first fibonacci numbers into a list.
<example>
(loop/list ((count (up-from 0 (to 100)))
(loop/list ((count (up-from 0 (:to 100)))
(a (in 0 b))
(b (in 1 (+ a b))))
b)
@ -192,15 +192,15 @@
<spec>
<syntax name="loop/first">
<form>(loop/first (clauses ...) body ...)</form>
<form>(loop/first [:default #f] (clauses ...) body ...)</form>
If any body is ever evaluated, stop and return the value of that evaluation. If no body is ever evaluated the return value is unspecified.
If any body is ever evaluated, stop and return the value of that evaluation. If no body is evaluated it returns the value specified by `:default`, which defaults to #f.
</syntax>
<syntax name="loop/last">
<form>(loop/last (clauses ...) body ...)</form>
<form>(loop/last [:default #f] (clauses ...) body ...)</form>
Returns the result of the last body to be evaluated. If no body is evaluated the return value is unspecified.
Returns the result of the last body to be evaluated. If no body is evaluated it returns the value specified by `:default`, which defaults to #f.
</syntax>
<syntax name="loop/list">
@ -392,69 +392,69 @@
<spec>
<syntax name="listing">
<form>(:acc binding (listing [(initial init)] expr [if guard]))</form>
<form>(:acc binding (listing [(:initial init)] expr [if guard]))</form>
Accumulates `expr` into a list. ´binding` is only accesible in the final-expression. The list is in the same order as the loop bodies were evaluated. If `initial` is given that will be used as the tail of the accumulated results. It defaults to `'()`.
</syntax>
<syntax name="listing-reverse">
<form>(:acc binding (listing-reverse [(initial init)] expr [if guard]))</form>
<form>(:acc binding (listing-reverse [(:initial init)] expr [if guard]))</form>
The same as `listing` but the resulting list in in reverse order. If the order of the resulting list does not matter, this will be faster than the regular listing as it will not preform any reverse at the end.
</syntax>
<syntax name="appending">
<form>(:acc binding (appending [(initial init)] expr [if guard]))</form>
<form>(:acc binding (appending [(:initial init)] expr [if guard]))</form>
`expr` evaluates to a list that is then appended to the accumulated result.
<example>
(loop ((:for elt (in-list '((1 2) (3 4))))
(:acc acc (appending (initial '(0)) elt)))
(:acc acc (appending (:initial '(0)) elt)))
=> acc)
;; => (0 1 2 3 4)
</example>
</syntax>
<syntax name="appending-reverse">
<form>(:acc binding (appending-reverse [(initial init)] expr [if guard]))</form>
<form>(:acc binding (appending-reverse [(:initial init)] expr [if guard]))</form>
`expr` evaluates to a list that is then consed element by element onto the already accumulated results. The default initial value is `'()`.
<example>
(loop ((:for elt (in-list '((1 2) (3 4))))
(:acc acc (appending-reverse (initial '(0)) elt)))
(:acc acc (appending-reverse (:initial '(0)) elt)))
=> acc)
;; => (4 3 2 1 0)
</example>
</syntax>
<syntax name="summing">
<form>(:acc binding (summing [(initial init)] expr [(if guard)]))</form>
<form>(:acc binding (summing [(:initial init)] expr [(if guard)]))</form>
Adds the result of `expr` together using `+`. The default initial value is 0.
</syntax>
<syntax name="multiplying">
<form>(:acc binding (multiplying [(initial init)] expr [(if guard)]))</form>
<form>(:acc binding (multiplying [(:initial init)] expr [(if guard)]))</form>
Multiplies the result of `expr` using `*`. The default initial value is 1.
</syntax>
<syntax name="hashing">
<form>(:acc binding (hashing [(initial init)] key value [(if guard)]))</form>
<form>(:acc binding (hashing [(:initial init)] key value [(if guard)]))</form>
Adds the mapping `(key => value)` to the hashtable `binding` using equal?-hashing. The initial hash table is an empty hash-table. `binding` is bound to the hash table throughout the loop, and its content can be mutated in the loop body.
</syntax>
<syntax name="hashving">
<form>(:acc binding (hashving [(initial init)] key value [(if guard)]))</form>
<form>(:acc binding (hashving [(:initial init)] key value [(if guard)]))</form>
Adds the mapping `(key => value)` to the hashtable `binding` using eqv?-hashing. The initial hash table is an empty hash-table. `binding` is bound to the hash table throughout the loop, and its content can be mutated in the loop body.
</syntax>
<syntax name="hashqing">
<form>(:acc binding (hashqing [(initial init)] key value [(if guard)]))</form>
<form>(:acc binding (hashqing [(:initial init)] key value [(if guard)]))</form>
Adds the mapping `(key => value)` to a hashtable using eq?-hashing. The initial hash table is an empty hash-table.`binding` is bound to the hash table throughout the loop, and its can be mutated in the loop body.
</syntax>

View file

@ -32,11 +32,15 @@
:when :unless :break :final :bind :do :subloop :for :acc
;; Auxiliary syntax for the iterators.
:gen
;; auxiliary syntax for some accumulators
:initial
;; auxiliary auxiliary syntax
;; for vectoring
:length :fill
;;for up-from and down-to
:to :by
;; used by for/first and for/last
:default
;; Internal syntax. %acc is turned into :acc by the forify macro
;; it is used make it possible to report an error if :acc is used in
;; one of the simple macros.
@ -491,27 +495,32 @@
=> acc
(product-loop)))))
(define sentinel (list 'unique))
;; TODO: maybe have a look at the expansion of this. It seems weird.
;; This exploits that we give the loop a name, but don't add the loop to the end of the
;; body, thus returning whatever the last expr of body returns.
(define-syntax loop/first
(syntax-rules ()
((n (clauses ...) body ...)
(syntax-rules (:default)
((n :default val (clauses ...) body ...)
(forify (n (clauses ...) body ...)
loop/first
() (clauses ... (:final #t))
=> #f
body ...))))
(define-syntax loop/last
(syntax-rules ()
() (clauses ...)
=> val
body ...))
((n (clauses ...) body ...)
(loop/first :default #f (clauses ...) body ...))))
;; unique value used for loop/last
(define sentinel (list 'unique))
(define-syntax loop/last
(syntax-rules (:default)
((n :default val (clauses ...) body ...)
(forify (n (clauses ...) body ...)
loop-name () (clauses ... (%acc acc (folding sentinel)))
=> (if (eq? sentinel acc) #f acc)
=> (if (eq? sentinel acc) val acc)
(let ((result (let () body ...)))
(loop-name (=> acc result)))))))
(loop-name (=> acc result)))))
((n (clauses ...) body ...)
(loop/last :default #f (clauses ...) body ...))))
(define-syntax loop/and
(syntax-rules ()

View file

@ -48,8 +48,10 @@
loop/list/parallel
:when :unless :break :final :bind :subloop :do :for :acc
:initial
:length :fill
:to :by
:default
in
in-list

View file

@ -373,10 +373,10 @@
(define-syntax accumulating
(syntax-rules (initial if :acc)
(syntax-rules (:initial if :acc)
((accumulating :acc (kons final init) ((var) . x) next . rest)
(accumulating :acc (kons final init) ((var cursor) . x) next . rest))
((accumulating :acc (kons final init) ((var cursor) ((initial i) . x)) n . rest)
((accumulating :acc (kons final init) ((var cursor) ((:initial i) . x)) n . rest)
(accumulating :acc (kons final i) ((var cursor) x) n . rest))
((accumulating :acc (kons final init) ((var cursor) (expr (if check))) n . rest)
(n ((tmp-kons kons))
@ -443,18 +443,18 @@
(syntax-rules ()
((_ name default-make setter)
(define-syntax name
(syntax-rules (:acc if initial)
(syntax-rules (:acc if :initial)
((_ :acc ((var) (key value)) n . rest)
(name :acc ((var) (key value (if #t) (initial default-make))) n . rest))
(name :acc ((var) (key value (if #t) (:initial default-make))) n . rest))
;; either init or if
((_ :acc ((var) (key value (if guard))) n . rest)
(name :acc ((var) (key value (if guard) (initial default-make))) n . rest))
(name :acc ((var) (key value (if guard) (:initial default-make))) n . rest))
((_ :acc ((var) (key value (initial init))) n . rest)
(name :acc ((var) (key value (if #t) (initial init))) n . rest))
(name :acc ((var) (key value (if #t) (:initial init))) n . rest))
;; both init and if
((_ :acc ((var) (key value (initial init) (if guard))) n . rest)
(name ((var) (key value (if guard) (initial init))) n . rest))
((_ :acc ((var) (key value (if guard) (initial init))) n . rest)
((_ :acc ((var) (key value (:initial init) (if guard))) n . rest)
(name ((var) (key value (if guard) (:initial init))) n . rest))
((_ :acc ((var) (key value (if guard) (:initial init))) n . rest)
(n
((var init))
((dummy (if #f #f) (if guard (setter var key value) (if #f #f))))

View file

@ -148,4 +148,9 @@
(loop/list ((a (up-from 1 (:by 2))) (b (in-list '(1 3 5))))
(+ a b))
'(2 6 10))
(test-equal "down-from-10"
(loop/sum ((a (down-from 11 (:to 1) (:by 2))))
a)
25)
(test-end ":for-clauses")