From 66e435aa9218d7c816c2ec602c2b922dce098414 Mon Sep 17 00:00:00 2001 From: Linus Date: Tue, 11 May 2021 11:14:22 +0200 Subject: [PATCH] updated the documentation Added stop-before, stop-after, in-cycle and in-indexed to the documentation. Also clarified (and examplified) the erathostenes example. --- documentation/doc.html | 33 +++++++++++++++++++------ documentation/doc.xml | 56 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/documentation/doc.html b/documentation/doc.html index 36910ab..7cf1d18 100644 --- a/documentation/doc.html +++ b/documentation/doc.html @@ -9,18 +9,31 @@
  • 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.
  • +
  • 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. Unless, of course, one considers CPS-conversion of arbitrary scheme code using syntax-rules simple.

    An example or two

    So, how does it look? A slightly contrived example, a naive sieve of Erathostenes:

     (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))
    +  (define vec (make-vector n #t))
    +  (loop/list ((:for i (up-from 2 (:to n)))
    +              (:when (vector-ref vec i)))
    +    ;; Here we set all multiples of i to #f
    +    (loop ((:for j (up-from (* 3 i) (:to n) (:by (* i 2)))))
    +      (vector-set! vec j #f))
    +    i))
             

    Calling (erathostenes 10) returns a list of all primes below 10.

    +

    The example above can also be written using “subloops”, but unless you know the expansion it can be somewhat surprising.

    +
    +(define (erathostenes n)
    +  (define vec (make-vector n #t))
    +  (loop ((:for i (up-from 2 (:to n)))
    +         (:acc lst (listing i))
    +         (:when (vector-ref vec i))
    +         (:for j (up-from (* 3 i) (:to n) (:by (* i 2))))
    +    => lst
    +    (vector-set! vec j #f)))
    +        

    Any :for clause following a :break, :when, :unless or :final clause is considered to be a subloop. Any :when clause also affects when accumulating clauses collect values. The expression following => is the final expression: this is the expression returned after the loop ends.

    Specification

    The loop grammar is the following:

     (loop [name] (loop-clause ...) [=> final-expr] body ...)
    @@ -133,7 +146,11 @@
     
     (loop/list (((_ . val) (in-hash hash-table)))
       val)
    -        

    :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.

    +
    Scheme syntax: in-cycle
    (:for binding (in-cycle iterator))

    Binds the values produced by iterator to binding. Once iterator stops, it starts over.

    +
    Scheme syntax: in-indexed
    (:for binding (in-indexed iterator))

    Binds binding to (n . value), where n is the nth value produced by iterator and value is that value. n starts from 0

    +
    Scheme syntax: stop-before
    (:for binding (stop-before iterator pred))

    Binds binding to the values produced by iterator until pred applied to that value returns true. The iterator is then considered exhausted. Useful in subloops where one might want to end internal iteration without :break-ing.

    +
    Scheme syntax: stop-after
    (:for binding (stop-after iterator pred))

    Binds binding to the values produced by iterator until pred applied to that value returns true. It then produces that last value. The iterator is then considered exhausted. Useful in subloops where one might want to end internal iteration without :break-ing.

    +

    :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:

    diff --git a/documentation/doc.xml b/documentation/doc.xml
    index 08761fa..3b9410b 100644
    --- a/documentation/doc.xml
    +++ b/documentation/doc.xml
    @@ -25,22 +25,40 @@
         * 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.
    +
    +    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. Unless, of course, one considers CPS-conversion of arbitrary scheme code using syntax-rules simple.
         
         
           So, how does it look? A slightly contrived example, a naive sieve of Erathostenes:
           
           
             (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))
    +          (define vec (make-vector n #t))
    +          (loop/list ((:for i (up-from 2 (:to n)))
    +                      (:when (vector-ref vec i)))
    +            ;; Here we set all multiples of i to #f
    +            (loop ((:for j (up-from (* 3 i) (:to n) (:by (* i 2)))))
    +              (vector-set! vec j #f))
    +            i))
             
     
             Calling `(erathostenes 10)` returns a list of all primes below 10.
    +
    +        The example above can also be written using "subloops", but unless you know the expansion it can be somewhat surprising.
    +
    +        
    +          (define (erathostenes n)
    +            (define vec (make-vector n #t))
    +            (loop ((:for i (up-from 2 (:to n)))
    +                   (:acc lst (listing i))
    +                   (:when (vector-ref vec i))
    +                   (:for j (up-from (* 3 i) (:to n) (:by (* i 2))))
    +              => lst
    +              (vector-set! vec j #f)))
    +        
    +
    +        Any :for clause following a :break, :when, :unless or :final clause is considered to be a subloop. Any :when clause also affects when accumulating clauses collect values. The expression following => is the final expression: this is the expression returned after the loop ends.
    +
         
       
     
    @@ -316,6 +334,30 @@
                 val)
             
           
    +
    +      
    +        
    (:for binding (in-cycle iterator))
    + + Binds the values produced by `iterator` to `binding`. Once `iterator` stops, it starts over. +
    + + +
    (:for binding (in-indexed iterator))
    + + Binds `binding` to `(n . value)`, where `n` is the nth value produced by `iterator` and `value` is that value. `n` starts from 0 +
    + + +
    (:for binding (stop-before iterator pred))
    + + Binds `binding` to the values produced by `iterator` until `pred` applied to that value returns true. The iterator is then considered exhausted. Useful in subloops where one might want to end internal iteration without :break-ing. +
    + + +
    (:for binding (stop-after iterator pred))
    + + Binds `binding` to the values produced by `iterator` until `pred` applied to that value returns true. It then produces that last value. The iterator is then considered exhausted. Useful in subloops where one might want to end internal iteration without :break-ing. +