Good point.

It is certainly undesirable that merely calling lseq-for-each forces all
elements in the argument, and I'm for reimplementing it.

Regarding the order of effects, I have an opinion.  The design choice of
lseq is to loosen the strict laziness to get efficiency.  Srfi-127 is
inspired by Gauche's lseq, which in turn is inspired by Clojure's seq.  I
believe Clojure may realize seq's elements in a chunk, e.g. 32 elements at
a time, at least in some situations.  So you shouldn't rely on the timing
of evaluation anyway; all that's guaranteed is that the data is available
by the time you accessed it, and the whole thing runs in a bounded space if
you don't keep the previous elements.  I think lseq should be used in the
same way, that is, you shouldn't rely on that (lseq-for-each write-char
chars) prints "read write read write ..."; it may print "read read write
write read read write ...".   But of course, you should be able to pass an
infinite lseq to lseq-for-each and expect proc to be definitely called.

Can this kind of semantic refinement go into post finalization notes?

--shiro




On Sat, Feb 11, 2023 at 8:12 AM Wolfgang Corcoran-Mathe <[email protected]>
wrote:

> Hi,
>
> The sample implementation of lseq-for-each has some surprising
> behavior. Here’s the implementation:
>
>     (define (lseq-for-each proc . lseqs)
>       (apply for-each proc (map lseq-realize lseqs)))
>
> Rather than traversing the lseqs and applying proc to their cars
> at each step, it fully forces them before traversing. There are
> two problems here.
>
> 1. Fully forcing the lseqs wastes space. A major reason to use
> lseqs is to avoid building huge lists. Thus, ignoring proc’s needs,
> lseq-for-each should operate in constant space.
>
> 2. Effects occur in an unexpected order. This is more interesting.
> Forcing the cdr of an lseq can have side effects, as can calling proc;
> indeed, proc is called for its side effects. When do these different
> effects occur? Consider this common kind of I/O stream:
>
>     (define chars (generator->lseq read-char))
>
> Calling ‘lseq-cdr’ on chars causes a side effect: reading a character.
> Now, combine this with an output procedure:
>
>     (lseq-for-each write-char chars)
>
> The read and write effects could occur in (at least) two different
> orders:
>
>     read
>     write
>     read
>     write
>     ...
>
> or
>
>     read
>     read
>     ...
>     read
>     write
>     write
>     ...
>     write
>
> The SRFI doesn’t tell us which to expect, but the latter, “clustered”
> pattern is bad. It will sponge up all available input before producing
> any output, hanging the program if ‘read-char’ blocks. “Interleaving”
> the effects, as in the first pattern, avoids these problems.
>
> ***
>
> The fix for both problems is simple: just implement ‘lseq-for-each’
> directly. I’ll send a pull request with this implementation shortly.
>
> I think a note about effect order should also be added to the
> ‘lseq-for-each’ specification. Perhaps this: “If forcing the cdr of
> lseq entails side effects, then those effects are interleaved with
> the effects of calling proc.”
>
> Regards,
>
> Wolf
>
> --
> Wolfgang Corcoran-Mathe  <[email protected]>
>

Reply via email to