Web Images Videos Maps News Shopping Gmail more »
Recently Visited Groups | Help | Sign in
Google Groups Home
Yow! LOOP macros are LOOPY!
There are currently too many topics in this group that display first. To make this topic appear first, remove this option from another topic.
There was an error processing your request. Please try again.
flag
  2 messages - Collapse all  -  Translate all to Translated (View all originals)
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
Alex Shinn  
View profile  
(6 users)  More options Sep 6 2006, 1:01 pm
Newsgroups: comp.lang.scheme
From: "Alex Shinn" <alexsh...@gmail.com>
Date: 6 Sep 2006 10:01:37 -0700
Local: Wed, Sep 6 2006 1:01 pm
Subject: Yow! LOOP macros are LOOPY!
Scheme was the first general purpose language to take the brave
stance of not providing any primitive iteration syntax.  From the
R5RS[1] introduction:

  By relying entirely on procedure calls to express iteration,
  Scheme emphasized the fact that tail-recursive procedure calls
  are essentially goto's that pass arguments.[2]

Despite this, people are eternally adding their own iteration
syntax to the language.  From the very beginning Scheme has in fact
included an iteration form, DO, borrowed from MacLisp; however it
was specifically intended as derived syntax, a thin wrapper around
a recursive procedure call.  Perhaps this was included as some sort
of defensive measure, the author worrying a language without
explicit iteration might not be taken seriously.  Or perhaps it was
intended to show how ugly iteration forms could be, encouraging
programmers to use and feel comfortable with manual recursion.
Certainly the latter seems to be the result, since very few people
actually use DO.

What does do do?  Do be do.

DO expresses the fundamental iteration concept in a manner similar
to but cleaner than C's FOR loop.  Consider

   (do ((ls '(1 3 5 7 9) (cdr ls))
        (sum 0 (+ sum (car ls))))
       ((null? ls) sum))

We have any number of loop variables each with an initial form and
step form, a termination condition, and optional return value and
body.  If you want to introduce a lexical scope with loop variables
this is about as simple and general as you can get (if you don't
want even scope then WHILE is the simplest iteration construct).

However, for practical purposes this is very limited.  The most
obvious problem, and the one solved by pretty much all other loop
macros, is convenience of iterating easily over different sequence
types.  If the above were summing over a vector instead of a list,
then the code would have to become

   (let* ((vec '#(1 3 5 7 9))
          (len (vector-length vec)))
     (do ((i 0 (+ i 1))
          (sum 0 (+ sum (vector-ref vec i))))
         ((= i len) sum)))

Changes are required throughout the code for only a simple
conceptual change in the design.  Shivers' LOOP macro discussed
below makes this point especially clear.

Don't be do.

So now, in addition to providing a scope and clean separation of
iteration phases, we want some abstraction over sequence types.  Of
other macros going around at the time other than MacLisp's
new-style DO, Common-Lisp of course adopted the most featureful,
the Common-Lisp LOOP[3].  It solves this problem and also provides
features such as aggregate functions to perform implicit summing or
collecting into lists, and explicit controls such as return and
goto.  It uses a keyword syntax which for simple examples makes it
look like broken English (this is a plus or minus, depending on who
you ask).  As a simple example, the following generates a list of
the numbers from 1 to 10:

  (loop for i from 1 to 10 collect i)

This is baroque, explicitly mutation and state-oriented, doesn't
nest properly, and is unextensible.  Yes, despite having all these
features, no more can be added.  Schemers tend to make fun of CL's
LOOP almost as much as they make fun of their own DO form.

Don't Loop, Iterate.

Jonathan Amsterdam's ITERATE macro[4] is an attempt to clean up
LOOP, removing some warts and giving it a more Lisp-like syntax.
Most notably, ITERATE is extensible.  You can portably add your own
syntax for iterating over new sequence types, such as your own
custom stream type or database query handles.  This is an important
feature that is shared by all the remaining macros we will look at.
The above example becomes

  (loop (for i from 1 to 10)
        (collect i))

One limitation of ITERATE is that you still need a RETURN form for
special conditional returns, and this doesn't nest cleanly without
tagbodies.  There are also certain patterns we'll see shortly where
it isn't convenient or efficient to update loop variables with
complex conditionals, such that you have to resort to binding them
outside the loop and using mutation.

Reducing to iteration.

Scheme48's approach[5] to iteration is to take a step back towards
named let, but add just enough to make iterating over sequence
types easier.  The ITERATE macro is essentially a named let where
certain of the variables are updated for you automatically.  For
example, to implement FIND from SRFI-1:

  (define (find pred ls)
    (iterate loop ((list* elt ls))   ; loop variables
         ()                          ; state variables
      (if (pred elt)                 ; body
          elt
          (loop))
      #f))                           ; [tail expression]

In this case there are no explicitly recursed variables (called
state variables), and so each time we recurse with just (LOOP), ELT
is bound to the next element of LS.  We terminate the loop as in
named let by simply choosing not to recurse, or by default
returning the optional tail expression when the list elements are
used up.  An example using state variables would be COUNT:

  (define (count pred ls)
    (iterate loop ((list* elt ls))
        ((sum 0))
      (if (pred elt)
          (loop (+ sum 1))
          (loop sum))
      sum))

Here SUM is a state variable - we manually recurse on that with
each iteration because the logic behind it can't be captured easily
behind some common iterator syntax.  In this case we are always
recursing with LOOP, relying on the list to be exhausted for
termination.  This common pattern is captured with the REDUCE
macro, by which the above form can be more succinctly written:

  (define (count pred ls)
    (reduce ((list* elt ls))
        ((sum 0))
      (if (pred elt)
          (+ sum 1)
          sum)))

and the list from 1 to 10 example becomes

  (reverse (reduce ((count* i 1 11))
               ((res '()))
             (cons i res)))

See the Scheme48 reference manual for a more thorough explanation
and examples.  The important point is that by returning to a more
natural Scheme flow-control, the iteration becomes easier to
follow, and at the same time more expressive.  And they are, of
course, easily extensible.  These macros deserve better recognition
in the Scheme community.

Comprehending iteration.

For certain very common patterns, many people prefer to name it
once and be done with.  It doesn't matter how COUNT is implemented
if you already know what it does.  Unfortunately, COUNT as is works
only on lists.  If you want to work with vectors you need
VECTOR-COUNT, and so on, and this approach doesn't work at all for
things like a combination of lists and vectors.

SRFI-42 "Eager Comprehensions"[6] attempts to solve this by not
only making the sequence iterators extensible, but by providing a
set of extensible iteration control structures.  So, for example,
there is a LIST-EC form to collect the sequence elements into a
list, and a SUM-EC to sum the sequence elements.  There is no
FIND-EC or COUNT-EC, but these could be defined.

  (list-ec (: i 1 11) i)

Compared to explicit named looping you lose some flexibility, and
you can't establish complex interactions between iterators or sum
at the same time as you collect a list.  The scope and timing of
bindings can also be unintuitive.

Anatomical correctness.

Shivers did a full analysis of loop macros in his "The Anatomy of a
Loop,"[7] and addresses the confusing scope issue with a
control-flow graph language.  The possible branches of the loop are
represented in a CFG, and the scope of variables can be traced by
their path through the graph.  This can represent any program, but
is too low-level to program in directly.  On top of this is built a
high-level extensible LOOP macro, based on another earlier macro
the Yale LOOP, which is similar to Amsterdam's ITERATE.  Thus the
recurring example becomes something like

  (loop (incr i from 1 to: 11)
        (save i))

A more complicated and impressive example, quicksort, is given
showing various features of the macro:

  (let recur ((left 0) (right (vector-length v)))
    (if (> (- right left) 1)
      (loop (initial (p (pick-pivot v left right))
                     (i (- left 1))
                     (j right))
            (subloop (incr i from i)
                     (bind (vi (vector-ref v i)))
                     (while (< vi p)))
            (subloop (decr j from j)
                     (bind (vj (vector-ref v j)))
                     (while (< p vj)))
            (until (<= j i))
            (do (vector-set! v i vj)
                (vector-set! v j vi))
            (after (recur left i)
                   (recur (+ j 1) right)))))

Although the CFG determines scope unambiguously, looking at the
high-level code it's not immediately obvious what happens when, and
which bindings are available where.  You can't simply trace the
indentation and nesting, since the far right indented VI introduced
with a BIND is later accessible in the outer DO.  It is also
interesting to note that the macro itself is incapable of handling
general recursion, requiring the entire form to be wrapped in a
named let.

Named let, named bindings.

With the exception of Scheme48, all of the loop macros require the
programmer to learn a domain language unrelated the Scheme.
Moreover, these domain languages still can't handle all general
forms of recursion, and it's possible that as your program becomes
more complicated you may have to rewrite from loop macros to
general recursion.

There are also some common patterns not addressed by any of these
macros.  One problem is that iteration over a sequence may be only
semi-regular.  For example, string traversals often have "escape"
characters, such as a backslash, which need to be processed
together with the next character.  Similarly in a VM or
state-machine you may want to step through one instruction at a
time, but may also have instructions which are jumps.  In both
cases you want to be able to override the default sequence
iteration when necessary.

Another problem that turns ...

read more »


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Abdulaziz Ghuloum  
View profile  
 More options Sep 8 2006, 7:11 pm
Newsgroups: comp.lang.scheme
From: "Abdulaziz Ghuloum" <aghul...@gmail.com>
Date: 8 Sep 2006 16:11:01 -0700
Local: Fri, Sep 8 2006 7:11 pm
Subject: Re: Yow! LOOP macros are LOOPY!
Alex,

Thank you for a nice write up about a nice idea.  This is perhaps the
most schemely loop that I've seen so far.

If I were able to (i.e. had 5 hours to spare), I would've written a
much longer criticism. :-)

Aziz,,,


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
End of messages
« Back to Discussions « Newer topic     Older topic »

Create a group - Google Groups - Google Home - Terms of Service - Privacy Policy
©2009 Google