Added basic documentation
WIP.
This commit is contained in:
parent
93aa5aa439
commit
969b9a029a
4 changed files with 789 additions and 149 deletions
154
documentation/build-doc.scm
Normal file
154
documentation/build-doc.scm
Normal file
|
@ -0,0 +1,154 @@
|
|||
;; Don't look too closely - code licensed under public domain.
|
||||
;;
|
||||
;; I'm sorry about this. I couldn't find my old xslt files and thought I might as well play with sxlt.
|
||||
;; this is the sorry result. guile-commonmark does not work with guile-3, so now we use pandoc to process markdown,
|
||||
;; which leads to all kinds of ugly things. Anyway, it builds doc.xml, just don't use it with anything else.
|
||||
;;
|
||||
;; doc.xml is xml+markdown. XML for the overall structure, and markdown for all sections, subsections and syntax
|
||||
;; elements.
|
||||
|
||||
(use-modules
|
||||
(srfi srfi-1)
|
||||
(srfi srfi-71)
|
||||
(ice-9 pretty-print)
|
||||
(ice-9 popen)
|
||||
(ice-9 textual-ports)
|
||||
(sxml match)
|
||||
(sxml xpath)
|
||||
(sxml simple)
|
||||
(sxml transform))
|
||||
|
||||
(define in-file "doc.xml")
|
||||
(define out-file "doc.html")
|
||||
|
||||
(define open-process (@@ (ice-9 popen) open-process))
|
||||
(define (pandoc-str str)
|
||||
(let ((in out err (open-process OPEN_BOTH "sh" "-c" "pandoc" "-f" "markdown" "-t" "html")))
|
||||
(display str out)
|
||||
(close-port out)
|
||||
(let* ((res (string-append "<markdown>" (get-string-all in) "</markdown>"))
|
||||
(res (cdadr (xml->sxml res))))
|
||||
(close-port in)
|
||||
res)))
|
||||
|
||||
(define (collapse-whitespace str)
|
||||
(let* ((strings (string-split str #\newline))
|
||||
(polished (map string-trim strings))
|
||||
(res (string-join polished "\n")))
|
||||
res))
|
||||
|
||||
(define (markdownize child)
|
||||
(cond
|
||||
((string? child)
|
||||
(let ((child (pandoc-str (collapse-whitespace child))))
|
||||
child))
|
||||
(else
|
||||
child)))
|
||||
|
||||
(define (read-document)
|
||||
(let* ((file (open-input-file in-file))
|
||||
(contents (get-string-all file)))
|
||||
(close-port file)
|
||||
(xml->sxml contents #:trim-whitespace? #t)))
|
||||
|
||||
(define (default . node)
|
||||
node)
|
||||
|
||||
(define (text tag . text)
|
||||
(car text))
|
||||
|
||||
(define (section->html . node)
|
||||
(sxml-match node
|
||||
((section (@ (title ,t)) . ,children)
|
||||
(let ((children (map markdownize children)))
|
||||
`(div (@ (id ,t)) (h2 ,t) . ,children)))
|
||||
((subsection (@ (title ,t)) ,children ...)
|
||||
(let ((children (map markdownize children)))
|
||||
`(div (@ (id ,t)) (h3 ,t) ,@children)))))
|
||||
|
||||
(define (unindent str)
|
||||
(define lines (string-split str #\newline))
|
||||
(define first-real (drop-while string-null? lines))
|
||||
(if (null? first-real)
|
||||
str
|
||||
(let* ((first-nonwhitespace (string-index (car first-real) (lambda (x) (not (char-whitespace? x)))))
|
||||
(trimmed-lines (map (lambda (x)
|
||||
(if (> (string-length x) first-nonwhitespace)
|
||||
(substring x first-nonwhitespace)
|
||||
x))
|
||||
lines)))
|
||||
(string-join trimmed-lines "\n"))))
|
||||
|
||||
(define (example->html tag str)
|
||||
`(pre (@ (class "code-example")) ,(unindent str)))
|
||||
|
||||
(define (verbatim->html tag str)
|
||||
`(pre ,(unindent str)))
|
||||
|
||||
|
||||
|
||||
(define (nullify . node)
|
||||
'())
|
||||
|
||||
(define (title->html node)
|
||||
(sxml-match node ((title ,t) `(h1 ,t))))
|
||||
|
||||
(define (author->html node)
|
||||
(sxml-match node
|
||||
((author (@ (email ,mail)) ,name)
|
||||
`(a (@ (href ,(string-append "mailto:" mail))) ,name))))
|
||||
|
||||
(define (syntax->html . node)
|
||||
(sxml-match node
|
||||
((syntax (@ (name ,name)) . ,rest)
|
||||
`((dt (a (@ (id ,name)) (b "Scheme syntax: "),name)
|
||||
(dd ,@(map markdownize rest)))))))
|
||||
|
||||
(define (form->html . node)
|
||||
(sxml-match node
|
||||
((form ,content)
|
||||
`((code ,content) (br)))))
|
||||
|
||||
|
||||
|
||||
(define (spec->html . node)
|
||||
`(dl ,@(cdr node)))
|
||||
(when (file-exists? out-file)
|
||||
(delete-file out-file))
|
||||
(define file (read-document))
|
||||
(define author ((sxpath '(doc metadata author)) file))
|
||||
(define title (car ((sxpath '(doc metadata title)) file)))
|
||||
(define html-title (title->html title))
|
||||
(define sections ((sxpath '(doc section)) file))
|
||||
(define doc
|
||||
`(html
|
||||
(head
|
||||
,title
|
||||
(style
|
||||
,(string-append
|
||||
"body { max-width: 7.6in; margin: 30pt;} "
|
||||
"pre { white-space: pre-wrap; }")))
|
||||
(body
|
||||
,html-title
|
||||
(div (@ (id "authors"))
|
||||
,@(map author->html author))
|
||||
,@sections)))
|
||||
|
||||
(define output
|
||||
(pre-post-order
|
||||
doc
|
||||
`((*default* . ,default)
|
||||
(*text* . ,text)
|
||||
(section . ,section->html)
|
||||
(subsection . ,section->html)
|
||||
(spec . ,spec->html)
|
||||
(syntax . ,syntax->html)
|
||||
(form . ,form->html)
|
||||
(verbatim . ,verbatim->html)
|
||||
(example . ,example->html))))
|
||||
(with-output-to-file out-file
|
||||
(lambda ()
|
||||
(display "<!DOCTYPE html>\n")
|
||||
(sxml->xml output)))
|
||||
|
||||
|
244
documentation/doc.html
Normal file
244
documentation/doc.html
Normal file
|
@ -0,0 +1,244 @@
|
|||
<!DOCTYPE html>
|
||||
<html><head><title>Goof-loop</title><style>body { max-width: 7.6in; margin: 30pt;} pre { white-space: pre-wrap; }</style></head><body><h1>Goof-loop</h1><div id="authors"><a href="mailto:linus.internet@fastmail.se">Linus Björnstam</a></div><div id="Preface"><h2>Preface</h2><p>We have all had the thought. <em>Why oh why are not all our problems solved by a nice recursive algorithm over a list?</em>. We see a neat problem. We smile and think about all the nice things we shall do right after finishing off this little tidbit, this delicious little bite of a problem. Then we scrape the surface. Under there is a jungle of state. Suddenly our little recursive list-mangling is not enough. To defeat the anxiety rising within us, we quickly write something using map, filter, zip and reduce. We test it. We smile again. We test it on real world data. The smile stiffens. The little bite has turned in to an o(n²) turd.</p>
|
||||
<p>We raise our gaze and see the disgustingly mutating little wonders we could have achieved using python list comprehensions. We look sideways and see the magnificent, fantastic, but abominable loop macro in Common Lisp. Even they, the elitist ultra-productive scumbags, hate it, yet they sieze every moment to make underhanded comments about named lets. Why, oh why is the world not as elegant as we want it to be?</p>
|
||||
<p>We think again about how python programmers write scheme, and shudder. <em>No! Never that!</em>, we think to ourselves. A vague idea starts forming. A quote surfaces from the depths of our minds <em>“Above all the wonders of Lisp’s pantheon stand its metalinguistic tools; by their grace have Lisp’s acolytes been liberated from the rigid ascetism of lesser faiths”</em>. Maybe we should try to do it ourselves. Thinking about it, we have actually written some macros before. Anaphoric ifs, some boilerplate removal macros, and oh! Even an almost-working implementation of SRFI-26. It passed nearly all the tests, and we couldn’t be bothered to go any further. But a looping facility… How hard can it be?</p>
|
||||
<p>– Linus Björnstam, September 2020</p>
|
||||
</div><div id="Introduction"><h2>Introduction</h2><p>Goof-loop is like the name suggests a looping facility. It is based in many ways on things I learned porting Racket’s for-loops to guile and on the marvel that is (chibi loop) in chibi-scheme. The goals of goof-loop are:</p>
|
||||
<ul>
|
||||
<li>Portability. Any sufficiently masochistic programmer should be able to implement it in r7rs syntax-rules</li>
|
||||
<li>A coherent, simple interface for looping over various kinds of data</li>
|
||||
<li>A coherent, simple intefrace for accumulating data in loops, with support for accumulating data over sub-loops</li>
|
||||
<li>A looping facility that in almost all cases produces as fast code as a hand-written named let</li>
|
||||
<li>An extensible looping facility, where new ways of iterating over data can be easily added The only other lisp looping facility that I know of that provides these things is Common Lisps iterate package. Iterate does however do a lot of things that are cumbersome to do in portable scheme, and would be prohibitively complicated to implement efficiently in a portable way.</li>
|
||||
</ul>
|
||||
<div id="An example or two"><h3>An example or two</h3><p>So, how does it look? A slightly contrived example, a naive sieve of Erathostenes:</p>
|
||||
<pre class="code-example">
|
||||
(define (erathostenes n)
|
||||
(define vec (make-vector n #t))
|
||||
(loop/list ((:for i (up-from 2 (to n)))
|
||||
:when (vector-ref vec i))
|
||||
(loop ((:for j (up-from (* 2 i) (to n) (by i))))
|
||||
(vector-set! vec j #f))
|
||||
i))
|
||||
</pre><p>Calling <code>(erathostenes 10)</code> returns a list of all primes below 10.</p>
|
||||
</div></div><div id="Specification"><h2>Specification</h2><p>The loop grammar is the following:</p>
|
||||
<pre>
|
||||
(loop [name] (loop-clause ...) [=> final-expr] body ...)
|
||||
|
||||
name = identifier
|
||||
|
||||
loop-clause = (:for id id* ... seq-expr)
|
||||
| (:acc id id* ... seq-expr)
|
||||
| :when guard-expr
|
||||
| :unless guard-expr
|
||||
| :break break-expr
|
||||
| :final guard-expr
|
||||
| :subloop
|
||||
|
||||
seq-expr = a macro that conforms to the looping protocol described below.
|
||||
|
||||
</pre><p>If a <code>name</code> is provided, it will be bound to a macro that allows named update of loop variables:</p>
|
||||
<pre class="code-example">
|
||||
(loop lp ((:for a (in 0 (+ a 1)))
|
||||
:break (> a 9))
|
||||
=> '()
|
||||
(if (= 4 a)
|
||||
(cons a (lp (=> a 8)))
|
||||
(cons a (lp))))
|
||||
</pre><p>This rather inane example would return <code>(0 1 2 3 4 8 9)</code>.</p>
|
||||
<div id="Subloops"><h3>Subloops</h3><p>A subloop is a distinction between an outer loop and an inner loop. An for each element yielded by an outer loop, the inner loop is run until exhaustion. All non :for- or :acc-clauses break out a subloop.</p>
|
||||
<pre class="code-example">
|
||||
(loop ((:for a (in-list '(1 2 3)))
|
||||
:subloop
|
||||
(:for b (up-from 0 (to a)))
|
||||
(:acc acc (listing (cons a b))))
|
||||
=> acc)
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
|
||||
</pre><p>The above <code>:subloop</code> clause is equivalent to <code>:when #t</code> and <code>:unless #f</code>. A <code>:break</code> clause will immediately stop execution of the loop:</p>
|
||||
<pre class="code-example">
|
||||
(loop ((:for a (in-list '(1 2 3)))
|
||||
:break (= 3 a)
|
||||
(:for b (up-from 0 (to a)))
|
||||
(:acc acc (listing (cons a b))))
|
||||
=> acc)
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1))
|
||||
</pre><p>And a :final guard will let one more body be evaluated. This clause is evaluated in the innermost loop, and as such only one body will be evaluated:</p>
|
||||
<pre class="code-example">
|
||||
(loop ((:for a (in-list '(1 2 3)))
|
||||
:final (= 3 a)
|
||||
(:for b (up-from 0 (to a)))
|
||||
(:acc acc (listing (cons a b))))
|
||||
=> acc)
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0))
|
||||
</pre></div><div id="Loop variables"><h3>Loop variables</h3><p>To be written</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)))
|
||||
(a (in 0 b))
|
||||
(b (in 1 (+ a b))))
|
||||
b)
|
||||
</pre><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>
|
||||
</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>
|
||||
</dd></dt><dt><a id="loop/product"><b>Scheme syntax: </b>loop/product</a><dd><code>(loop/product (clauses ...) body ...)</code><br /><p>Multiplies each evaluated body. If no body is evaluated, 1 is returned</p>
|
||||
</dd></dt><dt><a id="loop/sum"><b>Scheme syntax: </b>loop/sum</a><dd><code>(loop/sum (clauses ...) body ...)</code><br /><p>Sums the result of each evaluated body. If no body is evaluated, 0 is returned.</p>
|
||||
</dd></dt><dt><a id="loop/and"><b>Scheme syntax: </b>loop/and</a><dd><code>(loop/and (clauses ...) body ...)</code><br /><p>If all evaluated bodies return truthy, return the result of the last evaluated body. If any body returns #f, stop iteration and return #f. If no body is evaluated, #t is returned.</p>
|
||||
</dd></dt><dt><a id="loop/or"><b>Scheme syntax: </b>loop/or</a><dd><code>(loop/or (clauses ...) body ...)</code><br /><p>If any evaluated body returns truthy, stop iteration and return the result of that body. If no body returns truthy, return #f. If no body is evaluated, return #f.</p>
|
||||
</dd></dt><dt><a id="loop/list/parallel"><b>Scheme syntax: </b>loop/list/parallel</a><dd><code>(loop/list/parallel (clauses ...) body ...)</code><br /><p>Like loop/list, but evaluates each body in parallel.</p>
|
||||
</dd></dt></dl></div><div id=":for-clauses"><h3>:for-clauses</h3><dl><dt><a id="in"><b>Scheme syntax: </b>in</a><dd><code>(:for binding (in start [update [stop]]))</code><br /><p>Binds a loop variable to <code>binding</code>. It’s first value is <code>start</code>. It is updated by the <code>update</code> expression, or is left unchanged if no such expression is present. If a <code>stop</code> expression is provided, it will be evaluated before each loop body. If the <code>stop</code> expression returns true, the iteration will be considered exhausted.</p>
|
||||
<pre class="code-example">
|
||||
(loop ((:for a (in 0 b)) (:for b (in 1 (+ a b) (> b 20))))
|
||||
(display b) (newline))
|
||||
</pre><p>Will print all fibonacci numbers below 20.</p>
|
||||
</dd></dt><dt><a id="up-from"><b>Scheme syntax: </b>up-from</a><dd><code>(:for binding (up-from start [(to bound)] [(by step)]))</code><br /><code>(:for binding (up-from start [bound [by]]))</code><br /><p>Binds <code>binding</code> to the number <code>start</code> up to <code>bound</code> (exclusive!) by <code>step</code>. If no <code>bound</code> is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteratiom.</p>
|
||||
</dd></dt><dt><a id="down-from"><b>Scheme syntax: </b>down-from</a><dd><code>(:for binding (down-from start [(to bound)] [(by step)])</code><br /><code>(:for binding (down-from start [bound [by]]))</code><br /><p>Binds <code>binding</code> to the number <code>(- start 1)</code> down to <code>bound</code> (inclusive!) by <code>step</code>. If no <code>bound</code> is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteratiom.</p>
|
||||
</dd></dt><dt><a id="in-list"><b>Scheme syntax: </b>in-list</a><dd><code>(:for binding [pair] (in-list expr [by])</code><br /><p>Binds <code>binding</code> to the car of the loop variable <code>pair</code>. <code>pair</code> is advanced by applying the procedure <code>by</code> to it (defaulting to <code>cdr</code>). The iteration stops when <code>pair</code> is the empty list.</p>
|
||||
</dd></dt><dt><a id="in-lists"><b>Scheme syntax: </b>in-lists</a><dd><code>(:for binding [pairs] (in-lists expr [by])</code><br /><p>Works the same as <code>in-list</code>, but <code>expr</code> must evaluate to a list of lists. <code>binding</code> is bound to the car of those lists, and they are advanced by <code>by</code>, defaulting to <code>cdr</code>.</p>
|
||||
</dd></dt><dt><a id="in-vector"><b>Scheme syntax: </b>in-vector</a><dd><code>(:for binding [index] (in-vector expr [low [high]]))</code><br /><p>Binds <code>binding</code> to all elements in the vector produced by <code>expr</code> in order from <code>low</code> to <code>high</code>. <code>low</code> defaults to 0 and <code>high</code> defaults to the last index of the vector.</p>
|
||||
</dd></dt><dt><a id="in-reverse-vector"><b>Scheme syntax: </b>in-reverse-vector</a><dd><code>(:for binding [index] (in-reverse-vector expr [high [low]]))</code><br /><p>Binds <code>binding</code> to all elements in the vector produced by <code>expr</code> in reverse order from <code>high</code> to <code>low</code>. <code>high</code> defaults to the last element of the vector and <code>low</code> defaults to 0.</p>
|
||||
</dd></dt><dt><a id="in-string"><b>Scheme syntax: </b>in-string</a><dd><code>(:for binding [index] (in-string expr [low [high]]))</code><br /><p>Binds <code>binding</code> to all elements in the string produced by <code>expr</code> in order from <code>low</code> to <code>high</code>. <code>low</code> defaults to 0 and <code>high</code> defaults to the last index of the string.</p>
|
||||
</dd></dt><dt><a id="in-reverse-string"><b>Scheme syntax: </b>in-reverse-string</a><dd><code>(:for binding [index] (in-reverse-string expr [high [low]]))</code><br /><p>Binds <code>binding</code> to all elements in the vector produced by <code>expr</code> in reverse order from <code>high</code> to <code>low</code>. <code>high</code> defaults to the last element of the vector and <code>low</code> defaults to 0.</p>
|
||||
</dd></dt><dt><a id="in-port"><b>Scheme syntax: </b>in-port</a><dd><code>(:for binding (in-port port [reader [eof?]]))</code><br /><p>Binds <code>binding</code> to the result of calling <code>reader</code> on <code>port</code>. Iteration stops when <code>(eof? binding)</code> returns true.</p>
|
||||
</dd></dt><dt><a id="in-file"><b>Scheme syntax: </b>in-file</a><dd><code>(:for binding (in-file path [reader [eof?]]))</code><br /><p>Opens the file located at <code>path</code> (which is a string) and binds <code>binding</code> to the result of calling <code>reader</code> on the opened port. Iteration stops when <code>(eof? binding)</code> returns true.</p>
|
||||
</dd></dt><dt><a id="in-generator"><b>Scheme syntax: </b>in-generator</a><dd><code>(:for binding (in-generator gen))</code><br /><p>Binds binding to the result of calling the SRFI-158-compatible generator <code>gen</code>. Iteration stops when <code>gen</code> returns the end-of-file object.</p>
|
||||
</dd></dt><dt><a id="in-hash"><b>Scheme syntax: </b>in-hash</a><dd><code>(:for binding (in-hash hash))</code><br /><p>Binds <code>binding</code> to the <code>(key . value)</code> pairs of the hash-table <code>hash</code>. May, as all body-binding variables, be pattern-matched:</p>
|
||||
<pre class="code-example">
|
||||
(loop/list (((_ . val) (in-hash hash-table)))
|
||||
val)
|
||||
</pre></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>One general thing about accumulating clauses is that they all support a guarding <code>if</code> form. If such a clause is given, accumulation will only happen if the guard clause returns true. When a <code>:when</code> or <code>:unless</code> clause is given, they also have to return true for any result to be accumulated. The following code returns the empty list:</p>
|
||||
<pre class="code-example">
|
||||
(loop ((:for a (up-from 0 10))
|
||||
(:acc acc (listing a (if (odd? a))))
|
||||
:when (even? a))
|
||||
=> acc)
|
||||
</pre><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)
|
||||
;; => (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 class="code-example">
|
||||
(loop ((:for elt (in-list '((1 2) (3 4))))
|
||||
(:acc acc (appending-reverse (initial '(0)) elt)))
|
||||
=> acc)
|
||||
;; => (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 => value)</code> to the hashtable <code>binding</code> using equal?-hashing. The initial hash table is an empty hash-table.</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 => value)</code> to the hashtable <code>binding</code> using eqv?-hashing. The initial hash table is an empty hash-table.</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 => value)</code> to a hashtable using eq?-hashing. The initial hash table is an empty hash-table.</p>
|
||||
</dd></dt><dt><a id="vectoring"><b>Scheme syntax: </b>vectoring</a><dd><code>(:acc var [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>
|
||||
</dd></dt></dl></div></div><div id="Loop protocol"><h2>Loop protocol</h2><p>goof-loop is extensible using regular syntax-rules macros. The protocol for both :acc- and :for-clauses is identical, except that the behaviour of the different parts are slightly different.</p>
|
||||
<div id=":for-clauses"><h3>:for-clauses</h3><p>The following example defines the simple :for-driver <code>in-alist</code>:</p>
|
||||
<pre class="code-example">
|
||||
(define-syntax in-alist
|
||||
(syntax-rules ()
|
||||
((_ ((key val) (expr)) next . rest)
|
||||
(next
|
||||
;; Outer let bindings. These are bound before the loop body
|
||||
;; and are in the form ((id id* ... expr) ...).
|
||||
()
|
||||
;; Loop variables. Here we bind %cursor to expr, which at each iteration is updated
|
||||
;; by (cdr %cursor). In the form ((name init update) ...)
|
||||
((%cursor expr (cdr %cursor)))
|
||||
;; stop guards. In the form (stop-expr ...)
|
||||
((null? %cursor))
|
||||
;; Body bindings. If we need to do anything to the loop variables before
|
||||
;; any tests except the stop guards are run, this is where we do it.
|
||||
;; In the form ((bindings ... expr) ...) where the bindings may be
|
||||
;; (ice-9 match) patterns.
|
||||
(((key . val) (car %cursor)))
|
||||
;; This last one is finalizers. They run on any kind of exit from the same
|
||||
;; loop or any subloop where the :for-clause is in scope.
|
||||
() . rest))))
|
||||
</pre><p>In short, the clause (:for key value (in-alist alist-expr)) expands to:</p>
|
||||
<pre class="code-example">
|
||||
(in-alist ((key val) (alist-expr)) next-macro . rest)
|
||||
</pre><p>You almost never have to care about <code>rest</code>. That is the complete state of the expansion of loop, which we have to pass around since most of goof-loop is written in syntax-rules. (If you want to see how that is done, have a look at the source of <code>vectoring</code> which (ab)uses this to introduce a :break clause without breaking out a subloop).</p>
|
||||
<p>Going from the top we first have the outer let bindings. These are bound outside the loop, and are mostly used for binding things like vectors or ports that do not change during the loop.</p>
|
||||
<p>The next one are loop variables. Here we provide three things: variable name, initial expression and update. The update expression is used to bind the variable to the next value of the sequence.</p>
|
||||
<p>Stop guards are just that. In this case, when (null? %cursor)) returns true, the sequence is considered exhausted. If the loop is in a subloop, the current loop stops and the outer loop continues. If there is only one loop, it halts.</p>
|
||||
<p>Body bindings are bound before anything except the stop guards are run. They may be an (ice-9 match) pattern, and are multiple-value aware. <code>(a b (div-and-mod 10 7))</code> is valid, and so is ((key . val) (let ((c (car %cursor))) (values (car c) (cdr c)))).</p>
|
||||
<p>Finalizers are run everytime the current loop or any subloop below the current loop exits. This can be used to close resources. <code>(in-file ...)</code> uses this.</p>
|
||||
</div><div id=":acc-clauses"><h3>:acc-clauses</h3><p>The following code implements the accumulator (alisting …) which accumulates two values into an alist:</p>
|
||||
<pre class="code-example">
|
||||
(define-syntax alisting
|
||||
(syntax-rules (:acc)
|
||||
((_ :acc ((name) (key val)) next . rest)
|
||||
(next
|
||||
;; Outer let bindings. These are bound before the loop body
|
||||
;; and are in the form ((id id* ... expr) ...).
|
||||
()
|
||||
;; Loop variables. Here we bind %cursor to expr, which at each iteration is updated
|
||||
;; by (cdr %cursor). In the form ((name init update) ...)
|
||||
((%cursor '() (cons (cons key val) %cursor)))
|
||||
;; stop guards. Currently do nothing.
|
||||
()
|
||||
;; Body bindings. Not used much.
|
||||
()
|
||||
;; final-bindings. The same thing as wrapping the final-expr in
|
||||
;; (let ((name (reverse %cursor))) ...)
|
||||
((name (reverse %cursor)))
|
||||
. rest))))
|
||||
</pre><p>The first difference is that the first argument to an accumulator always is the symbol :acc.</p>
|
||||
<p>So, number one is the same as for :for-clauses. Number two looks the same, except the name %cursor and it’s initial value is promoted to the outermost loops. This is because the value of the accumulator needs to be available in all stages of the loop to be available in the final-expr. The initial value <em>cannot</em> therefore reference any things bound in outer loops.</p>
|
||||
<p>No loop currently use the stop guards, this is because the stop guards do not stop iteration completely, just end the current loop. This might change.</p>
|
||||
<p>The body bindings can be used to bind variables to make information from the accumulator visible, but otherwise not used.</p>
|
||||
<p>Final bindings. Binds whatever variable name you chose to whatever expression you chose in the final-expression.</p>
|
||||
</div></div><div id="Loop expansion"><h2>Loop expansion</h2><p>The main chunk of a loop expands into something like the following: A goof loop expands into something looking like this:</p>
|
||||
<pre>
|
||||
(let* (<outer-let> ...
|
||||
final-function (lambda (<final-binding>) <final-expr>))
|
||||
(let goof-loop ((<accumulator> <accumulator-init>) ... (<loop-var> <loop-var-init>) ...)
|
||||
(if (or <check> ...)
|
||||
(begin
|
||||
<for-clause-finalizer> ...
|
||||
(final-function (<accumulator-finalizer> <accumulator>) ...))
|
||||
(let ((<body-binding> ... <body-binding-expr>) ...)
|
||||
(let ((<user-binding> ... <user-binding-expr>) ...)
|
||||
(match-let ((<parenthesised-pattern> <match-expr>))
|
||||
(if (and <when-expr> ...)
|
||||
(cond
|
||||
((or <user-break> ...)
|
||||
<for-clause-finalizer> ...
|
||||
(final-function (<accumulator-finalizer> <accumulator>) ...))
|
||||
(else
|
||||
<loop-body>
|
||||
(goof-loop <accumulate> ... <loop-var-next> ...))
|
||||
(goof-loop <accumulator> ... <loop-var-next> ...)))))))))
|
||||
|
||||
<outer-let>: are provided by accumulators or for clauses for bindings that are not passed as an argument to the loop, for example a vector. The vector is bound here, and the index into the vector is the thing iterated over.
|
||||
|
||||
<final-binding> and <final-expr>: When the iteration ends, this function is called with the results of the :acc clauses. In the case of (:acc lst-acc (listing ...)), the name of the accumulator is never lst-acc in the loop body, but only in the <final-expr>. In case of (listing ...) the accumulated results are reversed before the final function.
|
||||
|
||||
<accumulator> and <loop-variable>: <accumulator> holds the current state of an accumulator clause. This is not necessarily the same binding as the user provided as the name, as described above. <loop-var> is the current state of a :for clause.
|
||||
|
||||
<check>: Checks for :for-clauses. In the case of (in-list ...) this would check for (not (pair? ...)).
|
||||
|
||||
<for-clause-finalizer>: some :for clauses need to be finalized. In the case of (in-file ...) the open file handle is closed at any point where the iteration stops.
|
||||
|
||||
<accumulator-finalizer>: <accumulator-finalizer> is any preprocessing done to <accumulator> before passing it on to the final-function. In the case of (listing ...) that would be (reverse ...).
|
||||
|
||||
<body-binding> and <body-binding-expr>:<body-binding> are the names the user provided for the body bindings. In the case of (:for a (in-list '(1 2 3))) the body binding would be (a (car name-of-loop-variable)). The body binding may be an (ice-9 match) pattern. More on that below.
|
||||
|
||||
<parenthesised-pattern> and <match-expr>: If a <user-binding> is not an identifier, it is presumed to be a match-let pattern. The result is bound to a variable and matched against this match-let.
|
||||
|
||||
<when-expr>: the user supplied :when or :unless guard expression.
|
||||
|
||||
<user-break>: user-supplied :break guard.
|
||||
|
||||
<loop-body>, <accumulate>, and <loop-var-next>: The user supplied body of the loop. If the loop is not named (i.e: in loops where the user controls the iteration) an expression for the next loop iteration is added to the body. <accumulate> is the expression the accumulator clause provided to accumulate a new value. For (:acc acc (listing elem)) that is (cons elem acc). <loop-var-next> is the expression evaluated to get the next iteration's loop variable. In the case of (in-list lst) that is (cdr lst). If a loop name is provided there is no implicit next loop.
|
||||
|
||||
<accumulator-init> and <loop-var-init>: <accumulator-init> are ALL accumulator init values, including the ones in subloops. For (listing ...) that is the empty list. <loop-var-init> is the initial loop vars.
|
||||
|
||||
|
||||
In case of subloops, those are placed instead of <loop-body>. They use the same final-function, and instead of quitting when any <check> triggers they go out to the outer loop.
|
||||
</pre></div></body></html>
|
|
@ -1,62 +1,51 @@
|
|||
<?xml version="1.0" ?>
|
||||
<doc>
|
||||
<metadata>
|
||||
<title></title>
|
||||
<title>Goof-loop</title>
|
||||
<author email="linus.internet@fastmail.se">Linus Björnstam</author>
|
||||
</metadata>
|
||||
<section title="Preface">
|
||||
<p>
|
||||
We have all had the thought. <em>Why oh why are not all our problems solved by a nice recursive algorithm over a list?</em>. We see a neat problem. We smile and think about all the nice things we shall do right after finishing off this little tidbit, this delicious little bite of a problem. Then we scrape the surface. Under there is a jungle of state. Suddenly our little recursive list-mangling is not enough. To defeat the anxiety rising within us, we quickly write something using map, filter, zip and reduce. We test it. We smile again. We test it on real world data. The smile stiffens. The little bite has turned in to an o(n²) turd.
|
||||
</p>
|
||||
We have all had the thought. *Why oh why are not all our problems solved by a nice recursive algorithm over a list?*. We see a neat problem. We smile and think about all the nice things we shall do right after finishing off this little tidbit, this delicious little bite of a problem. Then we scrape the surface. Under there is a jungle of state. Suddenly our little recursive list-mangling is not enough. To defeat the anxiety rising within us, we quickly write something using map, filter, zip and reduce. We test it. We smile again. We test it on real world data. The smile stiffens. The little bite has turned in to an o(n²) turd.
|
||||
|
||||
We raise our gaze and see the disgustingly mutating little wonders we could have achieved using python list comprehensions. We look sideways and see the magnificent, fantastic, but abominable loop macro in Common Lisp. Even they, the elitist ultra-productive scumbags, hate it, yet they sieze every moment to make underhanded comments about named lets. Why, oh why is the world not as elegant as we want it to be?
|
||||
|
||||
We think again about how python programmers write scheme, and shudder. _No! Never that!_, we think to ourselves. A vague idea starts forming. A quote surfaces from the depths of our minds *"Above all the wonders of Lisp's pantheon stand its metalinguistic tools; by their grace have Lisp's acolytes been liberated from the rigid ascetism of lesser faiths"*. Maybe we should try to do it ourselves. Thinking about it, we have actually written some macros before. Anaphoric ifs, some boilerplate removal macros, and oh! Even an almost-working implementation of SRFI-26. It passed nearly all the tests, and we couldn't be bothered to go any further. But a looping facility... How hard can it be?
|
||||
|
||||
<p>
|
||||
We raise our gaze and see the disgustingly mutating little wonders we could have achieved using python list comprehensions. We look sideways and see the magnificent, fantastic, but abominable loop macro in Common Lisp. Even they, the elitist ultra-productive scumbags, hate it, yet they sieze every moment to make underhanded comments about named lets. Why, oh why is the world not as elegant as we want it to be?
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We think again about how python programmers write scheme, and shudder. <em>"No! Never that!"</em>, we think to ourselves. A vague idea starts forming. A quote surfaces from the depths of our minds <em>"Above all the wonders of Lisp's pantheon stand its metalinguistic tools; by their grace have Lisp's acolytes been liberated from the rigid ascetism of lesser faiths"</em>. Maybe we should try to do it ourselves. Thinking about it, we have actually written some macros before. Anaphoric ifs, some boilerplate removal macros, and oh! Even an almost-working implementation of SRFI-26. It passed nearly all the tests, and we couldn't be bothered to go any further. But a looping facility... How hard can it be?
|
||||
</p>
|
||||
<p>
|
||||
-- Linus Björnstam, September 2020
|
||||
</p>
|
||||
|
||||
</section>
|
||||
|
||||
<section title="Introduction">
|
||||
<p>
|
||||
Goof-loop is like the name suggests a looping facility. It is based in many ways on things I learned porting Racket's for-loops to guile and on the marvel that is (chibi loop) in chibi-scheme. The goals of goof-loop are:
|
||||
<ul>
|
||||
<li> Portability. Any sufficiently masochistic programmer should be able to implement it in r7rs syntax-rules</li>
|
||||
<li> A coherent, simple interface for looping over various kinds of data </li>
|
||||
<li> A coherent, simple intefrace for accumulating data in loops, with support for accumulating data over sub-loops </li>
|
||||
<li> A looping facility that in almost all cases produces as fast code as a hand-written named let </li>
|
||||
<li> An extensible looping facility, where new ways of iterating over data can be easily added </li>
|
||||
</ul>
|
||||
|
||||
The only other lisp looping facility that I know of that provides these things is Common Lisps iterate package. Iterate does however do a lot of things that are cumbersome to do in portable scheme, and would be prohibitively complicated to implement efficiently in a portable way.
|
||||
</p>
|
||||
|
||||
Goof-loop is like the name suggests a looping facility. It is based in many ways on things I learned porting Racket's for-loops to guile and on the marvel that is (chibi loop) in chibi-scheme. The goals of goof-loop are:
|
||||
|
||||
* Portability. Any sufficiently masochistic programmer should be able to implement it in r7rs syntax-rules
|
||||
* A coherent, simple interface for looping over various kinds of data
|
||||
* A coherent, simple intefrace for accumulating data in loops, with support for accumulating data over sub-loops
|
||||
* A looping facility that in almost all cases produces as fast code as a hand-written named let
|
||||
* An extensible looping facility, where new ways of iterating over data can be easily added
|
||||
The only other lisp looping facility that I know of that provides these things is Common Lisps iterate package. Iterate does however do a lot of things that are cumbersome to do in portable scheme, and would be prohibitively complicated to implement efficiently in a portable way.
|
||||
|
||||
<subsection title="An example or two">
|
||||
<p>
|
||||
So, how does it look? A slightly contrived example, a naive sieve of Erathostenes:
|
||||
<example>
|
||||
(define (erathostenes n)
|
||||
(define vec (make-vector n #t))
|
||||
(loop/list ((:for i (up-from 2 (to n)))
|
||||
:when (vector-ref vec i))
|
||||
(loop ((:for j (up-from (* 2 i) (to n) (by i))))
|
||||
(vector-set! vec j #f))
|
||||
i))
|
||||
So, how does it look? A slightly contrived example, a naive sieve of Erathostenes:
|
||||
|
||||
<example>
|
||||
(define (erathostenes n)
|
||||
(define vec (make-vector n #t))
|
||||
(loop/list ((:for i (up-from 2 (to n)))
|
||||
:when (vector-ref vec i))
|
||||
(loop ((:for j (up-from (* 2 i) (to n) (by i))))
|
||||
(vector-set! vec j #f))
|
||||
i))
|
||||
</example>
|
||||
|
||||
Calling <code>(erathostenes 10) </code> returns a list of all primes below 10.
|
||||
</p>
|
||||
Calling `(erathostenes 10)` returns a list of all primes below 10.
|
||||
</subsection>
|
||||
</section>
|
||||
|
||||
<section title="Specification">
|
||||
<p>
|
||||
The loop grammar is the following:
|
||||
<example>
|
||||
|
||||
<verbatim>
|
||||
(loop [name] (loop-clause ...) [=> final-expr] body ...)
|
||||
|
||||
name = identifier
|
||||
|
@ -68,9 +57,12 @@
|
|||
| :break break-expr
|
||||
| :final guard-expr
|
||||
| :subloop
|
||||
</example>
|
||||
|
||||
If a <code>name</code> is provided, it will be bound to a macro that allows named update of loop variables:
|
||||
seq-expr = a macro that conforms to the looping protocol described below.
|
||||
|
||||
</verbatim>
|
||||
|
||||
If a `name` is provided, it will be bound to a macro that allows named update of loop variables:
|
||||
|
||||
<example>
|
||||
(loop lp ((:for a (in 0 (+ a 1)))
|
||||
|
@ -81,8 +73,8 @@
|
|||
(cons a (lp))))
|
||||
</example>
|
||||
|
||||
This rather inane example would return <code>(0 1 2 3 4 8 9)</code>.
|
||||
</p>
|
||||
This rather inane example would return `(0 1 2 3 4 8 9)`.
|
||||
|
||||
<subsection title="Subloops">
|
||||
A subloop is a distinction between an outer loop and an inner loop. An for each element yielded by an outer loop, the inner loop is run until exhaustion. All non :for- or :acc-clauses break out a subloop.
|
||||
|
||||
|
@ -95,7 +87,7 @@
|
|||
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0) (3 . 1) (3 . 2))
|
||||
</example>
|
||||
|
||||
The above <code>:subloop</code> clause is equivalent to <code>:when #t</code> and <code> :unless #f</code>. A <code>:break </code> clause will immediately stop execution of the loop:
|
||||
The above `:subloop` clause is equivalent to `:when #t` and ` :unless #f`. A `:break ` clause will immediately stop execution of the loop:
|
||||
|
||||
<example>
|
||||
(loop ((:for a (in-list '(1 2 3)))
|
||||
|
@ -114,11 +106,17 @@
|
|||
(:for b (up-from 0 (to a)))
|
||||
(:acc acc (listing (cons a b))))
|
||||
=> acc)
|
||||
;; =>((1 . 0) (2 . 0) (2 . 1) (3 . 0))
|
||||
;; => ((1 . 0) (2 . 0) (2 . 1) (3 . 0))
|
||||
</example>
|
||||
</subsection>
|
||||
|
||||
<subsection title="Loop variables">
|
||||
To be written
|
||||
</subsection>
|
||||
|
||||
|
||||
<subsection title="Simple forms">
|
||||
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.
|
||||
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)))
|
||||
|
@ -127,144 +125,389 @@
|
|||
b)
|
||||
</example>
|
||||
|
||||
|
||||
<dl>
|
||||
<dt type="syntax">(loop/first (clauses ...) body ...)</dt>
|
||||
<dd>
|
||||
<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 type="syntax">(loop/last (clauses ...) body ...)</dt>
|
||||
<dd>
|
||||
<p>
|
||||
Returns the result of the last body to be evaluated. If no body is evaluated the return value is unspecified.
|
||||
</p>
|
||||
</dd>
|
||||
<spec>
|
||||
<syntax name="loop/first">
|
||||
<form>(loop/first (clauses ...) body ...)</form>
|
||||
|
||||
<dt type="syntax">(loop/list (clauses ...) body ...)</dt>
|
||||
<dd>
|
||||
<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>
|
||||
</dd>
|
||||
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.
|
||||
</syntax>
|
||||
|
||||
<dt type="syntax">(loop/product (clauses ...) body ...)</dt>
|
||||
<dd>
|
||||
<p>
|
||||
Multiplies each evaluated body. If no body is evaluated, 1 is returned
|
||||
</p>
|
||||
</dd>
|
||||
<syntax name="loop/last">
|
||||
<form>(loop/last (clauses ...) body ...)</form>
|
||||
|
||||
Returns the result of the last body to be evaluated. If no body is evaluated the return value is unspecified.
|
||||
</syntax>
|
||||
|
||||
<syntax name="loop/list">
|
||||
<form>(loop/list (clauses ...) body ...)</form>
|
||||
|
||||
Iterates over `clauses` 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.
|
||||
|
||||
The list returned is the same even when used with multi-shot continuations.
|
||||
|
||||
<dt type="syntax">(loop/sum (clauses ...) body ...)</dt>
|
||||
<dd>
|
||||
<p>
|
||||
Sums the result of each evaluated body. If no body is evaluated, 0 is returned.
|
||||
</p>
|
||||
</dd>
|
||||
If no body is evaluated, the result is the empty list.
|
||||
</syntax>
|
||||
|
||||
<syntax name="loop/product">
|
||||
<form>(loop/product (clauses ...) body ...)</form>
|
||||
|
||||
<dt type="syntax">(loop/and (clauses ...) body ...)</dt>
|
||||
<dd>
|
||||
<p>
|
||||
If all evaluated bodies return truthy, return the result of the last evaluated body. If any body returns #f, stop iteration and return #f. If no body is evaluated, #t is returned.
|
||||
</p>
|
||||
</dd>
|
||||
Multiplies each evaluated body. If no body is evaluated, 1 is returned
|
||||
</syntax>
|
||||
|
||||
<syntax name="loop/sum">
|
||||
<form>(loop/sum (clauses ...) body ...)</form>
|
||||
|
||||
<dt type="syntax">(loop/or (clauses ...) body ...)</dt>
|
||||
<dd>
|
||||
<p>
|
||||
If any evaluated body returns truthy, stop iteration and return the result of that body. If no body returns truthy, return #f. If no body is evaluated, return #f.</p>
|
||||
</dd>
|
||||
Sums the result of each evaluated body. If no body is evaluated, 0 is returned.
|
||||
</syntax>
|
||||
|
||||
<dt type="syntax">(loop/list/parallel (clauses ...) body ...)</dt>
|
||||
<dd>
|
||||
<p>
|
||||
Like loop/list, but evaluates each body in parallel.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
</dl>
|
||||
<syntax name="loop/and">
|
||||
<form>(loop/and (clauses ...) body ...)</form>
|
||||
|
||||
If all evaluated bodies return truthy, return the result of the last evaluated body. If any body returns #f, stop iteration and return #f. If no body is evaluated, #t is returned.
|
||||
</syntax>
|
||||
|
||||
|
||||
<syntax name="loop/or">
|
||||
<form>(loop/or (clauses ...) body ...)</form>
|
||||
|
||||
If any evaluated body returns truthy, stop iteration and return the result of that body. If no body returns truthy, return #f. If no body is evaluated, return #f.
|
||||
</syntax>
|
||||
|
||||
<syntax name="loop/list/parallel">
|
||||
<form>(loop/list/parallel (clauses ...) body ...)</form>
|
||||
Like loop/list, but evaluates each body in parallel.
|
||||
</syntax>
|
||||
</spec>
|
||||
|
||||
</subsection>
|
||||
|
||||
|
||||
<subsection title=":for-clauses">
|
||||
<dl>
|
||||
<dt type="syntax">(:for binding (in start [update [stop]]))</dt>
|
||||
<dd>
|
||||
<p>
|
||||
Binds a loop variable to <code>binding</code>. It's first value is <code>start</code>. It is updated by the <code>update</code> expression, or is left unchanged if no such expression is present. If a <code>stop</code> expression is provided, it will be evaluated before each loop body. If the <code>stop</code> expression returns true, the iteration will be considered exhausted.
|
||||
|
||||
<example>
|
||||
(loop ((:for a (in 0 b)) (:for b (in 1 (+ a b) (> b 20))))
|
||||
(display b) (newline))
|
||||
</example>
|
||||
Will print all fibonacci numbers below 20.
|
||||
</p>
|
||||
</dd>
|
||||
<spec>
|
||||
<syntax name="in">
|
||||
<form>(:for binding (in start [update [stop]]))</form>
|
||||
Binds a loop variable to `binding`. It's first value is `start`. It is updated by the `update` expression, or is left unchanged if no such expression is present. If a `stop` expression is provided, it will be evaluated before each loop body. If the `stop` expression returns true, the iteration will be considered exhausted.
|
||||
|
||||
<dt type="syntax">(:for binding (up-from start [(to bound)] [(by step)])</dt>
|
||||
<dt type="syntax">(:for binding (up-from start [bound [by]]))</dt>
|
||||
<dd>Binds <code>binding</code> to the number <code>start</code> up to <code>bound</code> (exclusive!) by <code>step</code>. If no <code>bound</code> is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteratiom.</dd>
|
||||
<example>
|
||||
(loop ((:for a (in 0 b)) (:for b (in 1 (+ a b) (> b 20))))
|
||||
(display b) (newline))
|
||||
</example>
|
||||
|
||||
Will print all fibonacci numbers below 20.
|
||||
</syntax>
|
||||
|
||||
<dt type="syntax">(:for binding (down-from start [(to bound)] [(by step)])</dt>
|
||||
<dt type="syntax">(:for binding (down-from start [bound [by]]))</dt>
|
||||
<dd>Binds <code>binding</code> to the number <code>(- start 1)</code> down to <code>bound</code> (inclusive!) by <code>step</code>. If no <code>bound</code> is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteratiom.</dd>
|
||||
<syntax name="up-from">
|
||||
<form>(:for binding (up-from start [(to bound)] [(by step)]))</form>
|
||||
<form>(:for binding (up-from start [bound [by]]))</form>
|
||||
|
||||
<dt type="syntax">(:for binding [pair] (in-list expr [by])</dt>
|
||||
<dd>Binds <code>binding</code> to the car of the loop variable <code>pair</code>. <code>pair</code> is advanced by applying the procedure <code>by</code> to it (defaulting to <code>cdr</code>). The iteration stops when <code>pair</code> is the empty list.</dd>
|
||||
Binds `binding` to the number `start` up to `bound` (exclusive!) by `step`. If no `bound` is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteratiom.
|
||||
</syntax>
|
||||
|
||||
<dd type="syntax">(:for binding [pairs] (in-lists expr [by])</dd>
|
||||
<dt>Works the same as <code>in-list</code>, but <code>expr</code> must evaluate to a list of lists. <code>binding</code> is bound to the car of those lists, and they are advanced by <code>by</code>, defaulting to <code>cdr</code>.</dt>
|
||||
<syntax name="down-from">
|
||||
<form>(:for binding (down-from start [(to bound)] [(by step)])</form>
|
||||
<form>(:for binding (down-from start [bound [by]]))</form>
|
||||
|
||||
<dd type="syntax">(:for binding [index] (in-vector expr [low [high]]))</dd>
|
||||
<dt>Binds <code>binding</code> to all elements in the vector produced by <code>expr</code> in order from <code>low</code> to <code>high</code>. <code>low</code> defaults to 0 and <code>high</code> defaults to the last index of the vector.</dt>
|
||||
Binds `binding` to the number `(- start 1)` down to `bound` (inclusive!) by `step`. If no `bound` is given, it will yield values indefinitely. The second shorter form will not allow unbounded iteratiom.
|
||||
</syntax>
|
||||
|
||||
<dd type="syntax">(:for binding [index] (in-reverse-vector expr [high [low]]))</dd>
|
||||
<dt>Binds <code>binding</code> to all elements in the vector produced by <code>expr</code> in reverse order from <code>high</code> to <code>low</code>. <code>high</code> defaults to the last element of the vector and <code>low</code> defaults to 0.</dt>
|
||||
<syntax name="in-list">
|
||||
<form>(:for binding [pair] (in-list expr [by])</form>
|
||||
|
||||
<dd type="syntax">(:for binding [index] (in-string expr [low [high]]))</dd>
|
||||
<dt>Binds <code>binding</code> to all elements in the string produced by <code>expr</code> in order from <code>low</code> to <code>high</code>. <code>low</code> defaults to 0 and <code>high</code> defaults to the last index of the string.</dt>
|
||||
Binds `binding` to the car of the loop variable `pair`. `pair` is advanced by applying the procedure `by` to it (defaulting to `cdr`). The iteration stops when `pair` is the empty list.
|
||||
</syntax>
|
||||
|
||||
<dd type="syntax">(:for binding [index] (in-reverse-string expr [high [low]]))</dd>
|
||||
<dt>Binds <code>binding</code> to all elements in the vector produced by <code>expr</code> in reverse order from <code>high</code> to <code>low</code>. <code>high</code> defaults to the last element of the vector and <code>low</code> defaults to 0.</dt>
|
||||
<syntax name="in-lists">
|
||||
<form>(:for binding [pairs] (in-lists expr [by])</form>
|
||||
|
||||
<dd type="syntax">(:for binding (in-port port [reader [eof?]]))</dd>
|
||||
<dt>Binds <code>binding</code> to the result of calling <code>reader</code> on <code>port</code>. Iteration stops when <code>(eof? binding)</code> returns true.</dt>
|
||||
Works the same as `in-list`, but `expr` must evaluate to a list of lists. `binding` is bound to the car of those lists, and they are advanced by `by`, defaulting to `cdr`.
|
||||
</syntax>
|
||||
|
||||
<dd type="syntax">(:for binding (in-file path [reader [eof?]]))</dd>
|
||||
<dt>Opens the file located at <code>path</code> (which is a string) and binds <code>binding</code> to the result of calling <code>reader</code> on the opened port. Iteration stops when <code>(eof? binding)</code> returns true.</dt>
|
||||
<syntax name="in-vector">
|
||||
<form>(:for binding [index] (in-vector expr [low [high]]))</form>
|
||||
|
||||
<dd type="syntax">(:for binding (in-generator gen))</dd>
|
||||
<dt>Binds binding to the result of calling the SRFI-158-compatible generator <code>gen</code>. Iteration stops when <code>gen</code> returns the end-of-file object.</dt>
|
||||
Binds `binding` to all elements in the vector produced by `expr` in order from `low` to `high`. `low` defaults to 0 and `high` defaults to the last index of the vector.
|
||||
</syntax>
|
||||
|
||||
<dd type="syntax">(:for binding (in-hash hash))</dd>
|
||||
<dt>Binds <code>binding</code> to the <code>(key . value)</code> pairs of the hash-table <code>hash</code>. May, as all body-binding variables, be pattern-matched:
|
||||
<syntax name="in-reverse-vector">
|
||||
<form>(:for binding [index] (in-reverse-vector expr [high [low]]))</form>
|
||||
|
||||
Binds `binding` to all elements in the vector produced by `expr` in reverse order from `high` to `low`. `high` defaults to the last element of the vector and `low` defaults to 0.
|
||||
</syntax>
|
||||
|
||||
<syntax name="in-string">
|
||||
<form>(:for binding [index] (in-string expr [low [high]]))</form>
|
||||
|
||||
Binds `binding` to all elements in the string produced by `expr` in order from `low` to `high`. `low` defaults to 0 and `high` defaults to the last index of the string.
|
||||
</syntax>
|
||||
|
||||
<syntax name="in-reverse-string">
|
||||
<form>(:for binding [index] (in-reverse-string expr [high [low]]))</form>
|
||||
|
||||
Binds `binding` to all elements in the vector produced by `expr` in reverse order from `high` to `low`. `high` defaults to the last element of the vector and `low` defaults to 0.
|
||||
</syntax>
|
||||
|
||||
<syntax name="in-port">
|
||||
<form>(:for binding (in-port port [reader [eof?]]))</form>
|
||||
|
||||
Binds `binding` to the result of calling `reader` on `port`. Iteration stops when `(eof? binding)` returns true.
|
||||
</syntax>
|
||||
|
||||
<syntax name="in-file">
|
||||
<form>(:for binding (in-file path [reader [eof?]]))</form>
|
||||
|
||||
Opens the file located at `path` (which is a string) and binds `binding` to the result of calling `reader` on the opened port. Iteration stops when `(eof? binding)` returns true.
|
||||
</syntax>
|
||||
|
||||
<syntax name="in-generator">
|
||||
<form>(:for binding (in-generator gen))</form>
|
||||
|
||||
Binds binding to the result of calling the SRFI-158-compatible generator `gen`. Iteration stops when `gen` returns the end-of-file object.
|
||||
</syntax>
|
||||
|
||||
<syntax name="in-hash">
|
||||
<form>(:for binding (in-hash hash))</form>
|
||||
|
||||
Binds `binding` to the `(key . value)` pairs of the hash-table `hash`. May, as all body-binding variables, be pattern-matched:
|
||||
|
||||
<example>
|
||||
(loop/list (((_ . val) (in-hash hash-table)))
|
||||
val)
|
||||
</example>
|
||||
</dt>
|
||||
</syntax>
|
||||
</spec>
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection title=":acc-clauses">
|
||||
Accumulating clauses differ from :for-clauses in 2 significant ways. They have a final value available in the `final-expr`, 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.
|
||||
|
||||
Another small thing is that for some :acc-clauses, the `binding` may sometimes only be visible to the user in the `final-expr`, but like :for-clauses they sometimes offer the programmer to name the loop variables.
|
||||
|
||||
One general thing about accumulating clauses is that they all support a guarding `if` form. If such a clause is given, accumulation will only happen if the guard clause returns true. When a `:when` or `:unless` clause is given, they also have to return true for any result to be accumulated. The following code returns the empty list:
|
||||
|
||||
<example>
|
||||
(loop ((:for a (up-from 0 10))
|
||||
(:acc acc (listing a (if (odd? a))))
|
||||
:when (even? a))
|
||||
=> acc)
|
||||
</example>
|
||||
|
||||
<spec>
|
||||
<syntax name="listing">
|
||||
<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>
|
||||
|
||||
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>
|
||||
|
||||
`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)
|
||||
;; => (0 1 2 3 4)
|
||||
</example>
|
||||
</syntax>
|
||||
|
||||
<syntax name="appending-reverse">
|
||||
<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)
|
||||
;; => (4 3 2 1 0)
|
||||
</example>
|
||||
</syntax>
|
||||
|
||||
<syntax name="summing">
|
||||
<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>
|
||||
|
||||
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>
|
||||
|
||||
Adds the mapping `(key => value)` to the hashtable `binding` using equal?-hashing. The initial hash table is an empty hash-table.
|
||||
</syntax>
|
||||
|
||||
<syntax name="hashving">
|
||||
<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.
|
||||
</syntax>
|
||||
|
||||
<syntax name="hashqing">
|
||||
<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.
|
||||
</syntax>
|
||||
|
||||
<syntax name="vectoring">
|
||||
<form>(:acc var [index] (vectoring expr [(:length len) [(:fill fill)]]))</form>
|
||||
|
||||
Accumulates the result of `expr` into a vector. If `len` and `fill` is given the vector will be at most `len` elements long and any unfilled indexes will contain the element `fill`. The loop will exit when `len` elements have been accumulated.
|
||||
|
||||
If `length` is not given, the vector will be expanded as required.
|
||||
</syntax>
|
||||
</spec>
|
||||
</subsection>
|
||||
</section>
|
||||
<section title="Loop protocol">
|
||||
goof-loop is extensible using regular syntax-rules macros. The protocol for both :acc- and :for-clauses is identical, except that the behaviour of the different parts are slightly different.
|
||||
|
||||
<subsection title=":for-clauses">
|
||||
The following example defines the simple :for-driver `in-alist`:
|
||||
|
||||
<example>
|
||||
(define-syntax in-alist
|
||||
(syntax-rules ()
|
||||
((_ ((key val) (expr)) next . rest)
|
||||
(next
|
||||
;; Outer let bindings. These are bound before the loop body
|
||||
;; and are in the form ((id id* ... expr) ...).
|
||||
()
|
||||
;; Loop variables. Here we bind %cursor to expr, which at each iteration is updated
|
||||
;; by (cdr %cursor). In the form ((name init update) ...)
|
||||
((%cursor expr (cdr %cursor)))
|
||||
;; stop guards. In the form (stop-expr ...)
|
||||
((null? %cursor))
|
||||
;; Body bindings. If we need to do anything to the loop variables before
|
||||
;; any tests except the stop guards are run, this is where we do it.
|
||||
;; In the form ((bindings ... expr) ...) where the bindings may be
|
||||
;; (ice-9 match) patterns.
|
||||
(((key . val) (car %cursor)))
|
||||
;; This last one is finalizers. They run on any kind of exit from the same
|
||||
;; loop or any subloop where the :for-clause is in scope.
|
||||
() . rest))))
|
||||
</example>
|
||||
|
||||
In short, the clause (:for key value (in-alist alist-expr)) expands to:
|
||||
|
||||
<example>
|
||||
(in-alist ((key val) (alist-expr)) next-macro . rest)
|
||||
</example>
|
||||
|
||||
You almost never have to care about `rest`. That is the complete state of the expansion of loop, which we have to pass around since most of goof-loop is written in syntax-rules. (If you want to see how that is done, have a look at the source of `vectoring` which (ab)uses this to introduce a :break clause without breaking out a subloop).
|
||||
|
||||
Going from the top we first have the outer let bindings. These are bound outside the loop, and are mostly used for binding things like vectors or ports that do not change during the loop.
|
||||
|
||||
The next one are loop variables. Here we provide three things: variable name, initial expression and update. The update expression is used to bind the variable to the next value of the sequence.
|
||||
|
||||
Stop guards are just that. In this case, when (null? %cursor)) returns true, the sequence is considered exhausted. If the loop is in a subloop, the current loop stops and the outer loop continues. If there is only one loop, it halts.
|
||||
|
||||
Body bindings are bound before anything except the stop guards are run. They may be an (ice-9 match) pattern, and are multiple-value aware. `(a b (div-and-mod 10 7))` is valid, and so is ((key . val) (let ((c (car %cursor))) (values (car c) (cdr c)))).
|
||||
|
||||
Finalizers are run everytime the current loop or any subloop below the current loop exits. This can be used to close resources. `(in-file ...)` uses this.
|
||||
</subsection>
|
||||
|
||||
|
||||
<subsection title=":acc-clauses">
|
||||
The following code implements the accumulator (alisting ...) which accumulates two values into an alist:
|
||||
|
||||
<example>
|
||||
(define-syntax alisting
|
||||
(syntax-rules (:acc)
|
||||
((_ :acc ((name) (key val)) next . rest)
|
||||
(next
|
||||
;; Outer let bindings. These are bound before the loop body
|
||||
;; and are in the form ((id id* ... expr) ...).
|
||||
()
|
||||
;; Loop variables. Here we bind %cursor to expr, which at each iteration is updated
|
||||
;; by (cdr %cursor). In the form ((name init update) ...)
|
||||
((%cursor '() (cons (cons key val) %cursor)))
|
||||
;; stop guards. Currently do nothing.
|
||||
()
|
||||
;; Body bindings. Not used much.
|
||||
()
|
||||
;; final-bindings. The same thing as wrapping the final-expr in
|
||||
;; (let ((name (reverse %cursor))) ...)
|
||||
((name (reverse %cursor)))
|
||||
. rest))))
|
||||
</example>
|
||||
|
||||
The first difference is that the first argument to an accumulator always is the symbol :acc.
|
||||
|
||||
So, number one is the same as for :for-clauses. Number two looks the same, except the name %cursor and it's initial value is promoted to the outermost loops. This is because the value of the accumulator needs to be available in all stages of the loop to be available in the final-expr. The initial value _cannot_ therefore reference any things bound in outer loops.
|
||||
|
||||
No loop currently use the stop guards, this is because the stop guards do not stop iteration completely, just end the current loop. This might change.
|
||||
|
||||
The body bindings can be used to bind variables to make information from the accumulator visible, but otherwise not used.
|
||||
|
||||
Final bindings. Binds whatever variable name you chose to whatever expression you chose in the final-expression.
|
||||
|
||||
</subsection>
|
||||
</section>
|
||||
|
||||
<section title="Loop expansion">
|
||||
The main chunk of a loop expands into something like the following:
|
||||
A goof loop expands into something looking like this:
|
||||
<verbatim>
|
||||
(let* (<outer-let> ...
|
||||
final-function (lambda (<final-binding>) <final-expr>))
|
||||
(let goof-loop ((<accumulator> <accumulator-init>) ... (<loop-var> <loop-var-init>) ...)
|
||||
(if (or <check> ...)
|
||||
(begin
|
||||
<for-clause-finalizer> ...
|
||||
(final-function (<accumulator-finalizer> <accumulator>) ...))
|
||||
(let ((<body-binding> ... <body-binding-expr>) ...)
|
||||
(let ((<user-binding> ... <user-binding-expr>) ...)
|
||||
(match-let ((<parenthesised-pattern> <match-expr>))
|
||||
(if (and <when-expr> ...)
|
||||
(cond
|
||||
((or <user-break> ...)
|
||||
<for-clause-finalizer> ...
|
||||
(final-function (<accumulator-finalizer> <accumulator>) ...))
|
||||
(else
|
||||
<loop-body>
|
||||
(goof-loop <accumulate> ... <loop-var-next> ...))
|
||||
(goof-loop <accumulator> ... <loop-var-next> ...)))))))))
|
||||
|
||||
<outer-let>: are provided by accumulators or for clauses for bindings that are not passed as an argument to the loop, for example a vector. The vector is bound here, and the index into the vector is the thing iterated over.
|
||||
|
||||
<final-binding> and <final-expr>: When the iteration ends, this function is called with the results of the :acc clauses. In the case of (:acc lst-acc (listing ...)), the name of the accumulator is never lst-acc in the loop body, but only in the <final-expr>. In case of (listing ...) the accumulated results are reversed before the final function.
|
||||
|
||||
<accumulator> and <loop-variable>: <accumulator> holds the current state of an accumulator clause. This is not necessarily the same binding as the user provided as the name, as described above. <loop-var> is the current state of a :for clause.
|
||||
|
||||
<check>: Checks for :for-clauses. In the case of (in-list ...) this would check for (not (pair? ...)).
|
||||
|
||||
<for-clause-finalizer>: some :for clauses need to be finalized. In the case of (in-file ...) the open file handle is closed at any point where the iteration stops.
|
||||
|
||||
<accumulator-finalizer>: <accumulator-finalizer> is any preprocessing done to <accumulator> before passing it on to the final-function. In the case of (listing ...) that would be (reverse ...).
|
||||
|
||||
<body-binding> and <body-binding-expr>:<body-binding> are the names the user provided for the body bindings. In the case of (:for a (in-list '(1 2 3))) the body binding would be (a (car name-of-loop-variable)). The body binding may be an (ice-9 match) pattern. More on that below.
|
||||
|
||||
<parenthesised-pattern> and <match-expr>: If a <user-binding> is not an identifier, it is presumed to be a match-let pattern. The result is bound to a variable and matched against this match-let.
|
||||
|
||||
<when-expr>: the user supplied :when or :unless guard expression.
|
||||
|
||||
<user-break>: user-supplied :break guard.
|
||||
|
||||
<loop-body>, <accumulate>, and <loop-var-next>: The user supplied body of the loop. If the loop is not named (i.e: in loops where the user controls the iteration) an expression for the next loop iteration is added to the body. <accumulate> is the expression the accumulator clause provided to accumulate a new value. For (:acc acc (listing elem)) that is (cons elem acc). <loop-var-next> is the expression evaluated to get the next iteration's loop variable. In the case of (in-list lst) that is (cdr lst). If a loop name is provided there is no implicit next loop.
|
||||
|
||||
<accumulator-init> and <loop-var-init>: <accumulator-init> are ALL accumulator init values, including the ones in subloops. For (listing ...) that is the empty list. <loop-var-init> is the initial loop vars.
|
||||
|
||||
|
||||
In case of subloops, those are placed instead of <loop-body>. They use the same final-function, and instead of quitting when any <check> triggers they go out to the outer loop.
|
||||
</verbatim>
|
||||
</section>
|
||||
</doc>
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
;; (rl ((a 5)) () (((b . _) (cons 1 2))) (+ a b))
|
||||
((_ (l ...) (m ...) (((p . p-rest) expr) . clause-rest) body ...)
|
||||
(rl (l ... (dummy expr)) (m ... ((p . p-rest) dummy)) clause-rest body ...))
|
||||
|
||||
|
||||
((rl (l ...) (m ...) ((binding expr) . clause-rest) body ...)
|
||||
(rl (l ... (binding expr)) (m ...) clause-rest body ...))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue