For Heresy, I got to thinking about all the different special for loops in
Racket, and how I could generalize them into a single form, and what I
realized is that all you need for that is to just build in an accumulator.

Every for loop in Heresy contains the inbuilt variable "cry", which is a
value that is passed forward to each subsequent iteration of the loop, and
can be assigned to explicitly with the "carry" keyword. When the loop
reaches its final iteration, it returns the value of "cry".

If I need to carry more than one value forward at once then, I just use an
object as "cry".

Heresy doesn't have vectors yet (though I could call out of course to
Racket and use its functions of course), but here's a list-based solution:

(def fn init-vec (n)
  ((for (x in (range 0 to n) with (thing (acc '())
                                         (vec '())))
     (carry (cry `(,(join 'zzz (cry 'acc))
                   ,(append (cry 'vec) (list (cry 'acc)))))))
   'vec))

(init-vec 3)
;=> '(() (zzz) (zzz zzz) (zzz zzz zzz))

It's wordier, obviously, but the trade-off is you can do some incredibly
powerful stuff with it. Earlier in the year I was working on
implementations for stuff like Clojure's threading macro (just iterating
over a list of functions and applying them to the start value stored in
cry), to a full-blown implementation of Haskell-like do notation (by using
an object in cry as a name-space and doing some macro magic to bind the
names for local use).

It also means having one for loop, that can be customized and written with
as much or as little logic as you need, instead of a whole library full of
them.

On Fri, Mar 11, 2016 at 11:21 PM, Matthias Felleisen <matth...@ccs.neu.edu>
wrote:

>
> Three observations:
>
> 1. The counter-argument to the RnRS quotation would be that
> for/vector/accum is that it expands into a plain old use of the same
> feature, because "it's just a macro."
>
> 2. People designed all kinds of 'loops' in the 70s to find just the right
> one: for/while/repeat/do and all of them with various
> pre/post/exit/continue/break options so that the invariant-wp logicians
> would be happy (and there is something to be said for this). Few of these
> loops survived because designers didn't want them in their languages.
>
> If you solve the same problem functionally, you often get away with the
> combination of two orthogonal features: tail-calls and accumulators. With
> that, cond and non-recursion in the proper clause.
>
> 3. Pyret's solution is to separate the 'for' from the 'vector' part and I
> think one could also separate the 'accum' part from those two. Then the
> whole thing may read like this:
>
>  (for vector (accum [t '()] ...)
>
> And to create a similar list structure, you'd write
>
>  (for list (accum [t '()] ...)
>
> And then you might ask whether (a) this affects performance and (b) such
> for loops can be abstracted over the second aspect, as in,
>
>   (lambda (build) (for build (accum [t '()]) ...)
>
>
> -- Matthias
>
> p.s. I like the elegance of your solution.
>
>
>
>
>
>
>
> On Mar 11, 2016, at 1:02 PM, 'John Clements' via Racket Users <
> racket-users@googlegroups.com> wrote:
>
> > Often, mutation provides “obvious” ways to do things that may be more
> difficult to do without it. Here’s one that I came across today: for/vector
> with an accumulator.
> >
> > In this case, I want to create an array of length ’n’ where each cell
> contains a list of n copies of the symbol ‘zzz. That is,
> >
> > (check-equal? (init-vec 3)
> >  ‘#(() (zzz) (zzz zzz) (zzz zzz zzz)))
> >
> > … and yes, the vector is of length n+1. No worries.
> >
> > The ‘natural’ way to do this is with a helper function that just
> recomputes a list of ’n’ copies of ‘zzz’. This does actually turn a linear
> algorithm into an n^2 one, though.
> >
> > There’s also a super-easy solution using for/vector with mutation:
> >
> > #lang racket
> >
> > (require rackunit)
> >
> > ;; construct a vector of lists of 'zzzs
> > (define (init-vec n)
> >  (define t '())
> >  (for/vector ([i (in-range (add1 n))])
> >    (begin0 t
> >            (set! t (cons 'zzz t)))))
> >
> > (check-equal? (init-vec 3)
> >              '#(() (zzz) (zzz zzz) (zzz zzz zzz)))
> >
> >
> > … but the “right” solution sounds like a for/vector with an accumulator,
> so that I could write:
> >
> > ;; construct a vector of lists of 'zzzs
> > (define (init-vec n)
> >  (for/vector/accum ([t '()]) ([i (in-range (add1 n))])
> >    (values t (cons 'zzz t))))
> >
> > Honestly, this feels a lot like one of Will Clinger’s “piling feature
> upon feature” situations, where the problem is that list comprehensions
> aren’t as flexible as they could be.
> >
> > Aaand, naturally, apologies in advance if this feature already exists;
> about half the time when I ask for a feature in Racket, someone added it
> last year and I just didn’t notice :).
> >
> > Back to your regularly scheduled program,
> >
> > John Clements
> >
> > P.S.: using for/fold to produce a list and then using list->vector is
> obviously still linear; I just like the idea of using something like
> for/vector that doesn’t produce the intermediate list. Seems like it should
> be possible.
> >
> >
> >
> > --
> > You received this message because you are subscribed to the Google
> Groups "Racket Users" group.
> > To unsubscribe from this group and stop receiving emails from it, send
> an email to racket-users+unsubscr...@googlegroups.com.
> > For more options, visit https://groups.google.com/d/optout.
>
> --
> You received this message because you are subscribed to the Google Groups
> "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to racket-users+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to