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