Is there a simple way to do lookahead in a Stream? One can of course buffer “on the side” one element in advance, but this only works for “100% custom” players etc. For example, how can I just “look” at the next element of an EventStreamPlayer’s stream? I can’t do ~esp.player.copy.next(()) as streams don’t really have copy semantics, i.e. that still consumes an element from the original stream, despite the copy…
More concrete example
Pdef(\bah, PmonoArtic(\default, \degree, Pseq([1, 2, 3])))
fork { Pdef(\bah).play; 1.3.wait; Pdef(\bah).pause } // plays two notes, then pauses
Pdef(\bah).player.originalStream.copy.next(()) // 'degree': 3 returned here
Pdef(\bah).player.originalStream.copy.next(()) // nil now
Even something much more trivial doesn’t work to copy; even deepCopy doesn’t help:
r = r { (1..5) do: _.yield; }
r.copy.next // 1
r.copy.next // 2
r.deepCopy.next // 3
r.deepCopy.next // 4
This simply isn’t in the design of Streams. There’s no future to look into: the future comes into being only by asking for the next value – which has already altered the stream’s state.
The buffering idea is basically the only way: you still have to go into the future, but with memory of the past.
I also don’t know if this is possible on Streams. At least it is possible on Patterns:
Pseq([0,1,2,3]).asStream.next // always 0, as Stream is always a copy
Prout{ (0..10).do(_.yield)}.asStream.next // same, embedding a custom routine function
The trick seems to be that .asStream actually creates a new stream.
One way to do it with routines would be to create a new routine from the same function, if we only had read access to its internal function (it’s a read-only variable).
OK, so, what I meant was a contrast between SC’s concept of a stream (which maintains only its present state, and can only advance to the next state) and a pure-functional language’s concept of a lazily-evaluated sequence of values.
For example, in Haskell, you can say:
numbers1 = 1 : map (+1) numbers1
(Sidenote: I don’t actually know Haskell – I cribbed this from Infinite list tricks in Haskell | TechRepublic – but I find this kind of recursive definition of a list immensely fascinating. Haskell and LISP are the only languages I would seriously consider starting over with.)
This would behave like a list, so you can increment or decrement an index at will, or take subsets from any position at any time – but the definition is theoretically infinite, unlike SC Array.series(1, 1, 1000). Being infinite is like an SC stream, but random access is built into the infinite-list concept, in a way that it simply isn’t built into SC’s streams.
Daniel’s answer, in general terms, points to a standard object-oriented approach to problems like this: rather than modifying the concept of a stream to include random access, he’s created a wrapper around a stream that supports random access.
“Wrapper around” is an extremely important idea – often/usually better than glomming features onto base classes themselves.
Wouldn’t it be nice though to be able to fork the same present into parallel or divergent futures?