[racket] stream-cons from racket/stream isn't lazy

From: Eli Barzilay (eli at barzilay.org)
Date: Sat Mar 5 22:32:54 EST 2011

5 hours ago, Matthias Felleisen wrote:
> I am interested in the following question: does it make sense to
> write parts of a systems in Lazy (so that you have lists=streams and
> you naturally stay in this world) and yet by linking to the strict
> world, you still get the best of both.

Four hours ago, Eugene Toder wrote:
> I see how this can make sense, however I'm not sure how to make it
> work. For example:
> > (define (foo) (display "called\n") empty-stream)
> > (cons 1 (foo))
> '(1 . #<promise:temp23>)
> > (stream-cons 1 (foo))
> called
> #<sequence>

As you said earlier, to get streams (in the conventional sense), you
don't need to do anything except for using plain list functions (in a
lazy module).

The `stream-cons' is unrelated to lazy lists for the moment.  It's an
interface that intends to expose iteration sequences as more
convenient first class values -- but doing that leads to some serious
performance issues that needs to be resolved.  (And doing that will
make sequences behave more like lazy lists, and lazy lists will be
acceptable sequences -- this is why the `stream' name.)

So you shouldn't use it at all.  (Unless you really know what you're
doing -- even if you want to deal with sequences directly, the library
will make some sequences slow enough to make coding with them

> So stream-cons is strict even in lazy racket, perhaps because
> stream-cons is defined in strict racket?

The way the lazy language works, any function that is defined outside
of the lazy world (and is not a struct constructor) is considered
eager, so arguments that are sent to it are forced -- which makes
using them as eager as they are in the plain language.  (That's
something to know to do the code splitting that Matthias is talking

> If I switch to lists, this works as expected:
> > (define (make-lazy-list)
>     (define (next n) (cons n (next (+ n 1))))
>     (next 0))
> > (display (take 5 (make-lazy-list)))
> (0 1 2 3 4)
> However using make-lazy-list from non-lazy racket gives an error:
> > (display (take (make-lazy-list) 5))
> take: index 5 too large for list (not a proper list): #<promise>
> Which is kind of expected, since strict racket functions are not
> prepared to deal with promises in arbitrary places.

Right -- on the strict side you'd want to use forces when needed.  If
you (require lazy/force) you get things like `!' (plain force), `!!'
(recursive force), `!!!' (recursuve force and wrap functions found in
the value), `!list' (force a list's chain, eg, so `length' works),
`!!list' (also forces the immediate values in a list).

> Also, my original question is about streams in non-lazy racket.
> Lazy/strict racket interaction is really a separate topic.  [...]
> What's the recommended way of getting lazy behaviour in (non-lazy)
> racket?

If you're using the srfi, you get a ton of `stream-*' functionality.
There's enough of that that it can be considered a language in itself,
only done with the bad (nonexistent) namespace management that scheme
limits you to.  The lazy language is taking this to an extreme and
adds more lazy functions, and uses the racket module system to avoid
the namespace issues.

Three hours ago, Mark Engelberg wrote:
> racket/stream is not really a "stream library" by the typical Scheme
> definition of stream.  It's really a library to manipulate Racket's
> sequence abstraction with list-like functions.  The naming is
> misleading.  The whole library is experimental and is likely to
> change and you shouldn't really be using it anyway.

To clarify, the library will stay, it will just do more (both more
functionality, and the existing functionality should become sane in
terms of efficiency).  As for the misleading name -- it will
eventually work with lazy lists too, which is the reason for the name.

> Racket does not have a built-in stream library that integrates
> nicely with its sequence abstraction [...]

Yes -- and that's bad.

> a) There are tons of things competing for their development time,
>    and something like a stream library is exactly the kind of thing
>    that could be easily written by a motivated user.

This would be very difficult.

The internal representation of sequences should change to make other
kinds of sequences available, these kinds should include either
state-based producers, simple thunked lazy lists, or memoized thunks
(what is usually known as streams).  More likely, all more than one of
these will be needed (for example, a state-based producer can be much
faster for simple numeric sequences, but problematic due to its state;
also, for numeric ranges a simple non-memoized thunked stream can be
faster than a memoized one, since computing the next number is faster
than dealing with the cache).  To make things more fun, there should
be another form of a stream, which you'd get when you append two
streams (even if they're the same kind).  I think that some of the
work that Hari did on functional data structures will be good here, in
finding some efficient implementation for this.

Two hours ago, Eugene Toder wrote:
> [...] This confuses people who use racket to learn scheme by working
> through SICP book.

I thought that there was some warning there...  I'll add one.

          ((lambda (x) (x x)) (lambda (x) (x x)))          Eli Barzilay:
                    http://barzilay.org/                   Maze is Life!

Posted on the users mailing list.