Lookahead in a Stream (without "consuming" its next element?)

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.

hjh

1 Like

Check out this discussion:

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).

Wouldn’t it be nice though to be able to fork the same present into parallel or divergent futures?

1 Like

For duplicating Patterns you can check PSdup from miSCellaneous_lib.

But actually, as James mentioned, you need buffering primarily. PSx pattern also have a buffering option.

p = PS(Pwhite(0, 100), inf, 10);
q = p.iter

q.nextN(10)

-> [ 17, 67, 28, 17, 31, 86, 5, 11, 35, 80 ]

p.lastValues

-> [ 80, 35, 11, 5, 86, 31, 17, 28, 67, 17 ]

p[1]

-> 35

This also works for event patterns. Hope this helps.

2 Likes

Surely possible in other paradigms. https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/tee

There’s actually a suggestion in the OCaml manual how to implement this in general (their streams don’t support a clone either): https://ocaml.org/learn/tutorials/streams.html#Copying-streams

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 https://www.techrepublic.com/article/infinite-list-tricks-in-haskell/ – 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?

Sure. RFluff mentioned https://ocaml.org/learn/tutorials/streams.html#Copying-streams – we could do the same in SC. That’s a perfect example of a “wrapper around.”

hjh

FPlib quark has a class for infinite lists (and a number of other Haskellisms) - LazyList IIRC