-
Notifications
You must be signed in to change notification settings - Fork 27
Description
The scan function seems to be both common and useful,
but it also caused me some confusions like here and here that I'd like to clear if possible.
Here is my current understanding (appreciate any correction):
- The
scanfunction inflydis impure, strictly speaking. The simplest possible example is
const s = flyd.stream()
const s1 = flyd.scan(`add`, 1, s)
s1() //=> 1
s(1)
const s2 = flyd.scan(`add`, 1, s)
s2() //=> 2So the result depends on the time of calling the scan.
-
I would like to run the same example with
Hareactive, but at the moment it is not quite clear to me what would be the best way. From the scan tests I see that asinkStreamis used to create a "ProducerStream", thenscanis followed the.at()evaluation, and then bysubscribe, whose role is not quite clear to me, in particular, whether it turns stream from pending into active, like other libraries do. Then events need to be published. I wonder if there were any more direct way similar to the above. -
It can be seen as composition of truncating the stream events between two moments into array (forgetting the times) and subsequent
reduce. The latter part is pure. The impurity comes from the implicit dependence on the first moment (the moment when thescanwas called). It is still pure in the second moment, which is the current time. -
The
scanbecomes pure when applied to the so-called "cold" (I find "pending" more descriptive) streams, the ones that had not received any value yet. This is how they do it in MostJS. Any of their method "activating" the stream, transforms it into Promise, after which methods likescanare not allowed. That wayscanremains pure. -
Applying
scanonly to the pending streams is the most common use case, as e.g. @JAForbes was suggesting in Allow optional seed in Stream.scan? MithrilJS/mithril.js#1822 . Such as the action stream is passed toscanbefore at the initialisation time, whereas the actions begin to arrive after. This fact is also confirmed by the absence of tests in the non-pending cases, for instance, note how in https://github.com/paldepind/flyd/blob/master/test/index.js#L426 the stream is always created empty. -
The 2
scanmethods here are pure, however, they differ from other libraries in which they return the more complex types of eitherBehavior<Stream<A>>orBehavior<Behavior<A>>.
The implementation (as always) varies among libraries:
-
flyd(andmithril/stream) allowsscanon any stream and returns stream -
MostJS
scanregards all Streams as "cold", with the additionalmosj-subjectto use with active streams, however, the purity is lost in that case. -
Xstream does not have
scan, it seems to be replaced with thefoldwhich "Combines events from the past throughout the entire execution of the input stream". That seems to solve the purity problem for them, but may not be as convenient to use. -
KefirJS https://rpominov.github.io/kefir/#scan and BaconJS https://baconjs.github.io/api.html let their
scanto transform stream into
what they call "property", which I understand is the same as Behavior here.
I am not familiar with details but they seem to talk about "active", so possibly they way is similar to MostJS. -
The
RxJSmakes theseedargument optional. Which, however, presents problems if it is of different type than the accumulator. (They only demonstrate the simple addition case, where the types are equal.)
The same is in KefirJS
Possible suggestions:
-
Change the name to something like
pureScanto emphasise both the difference and the motivation for the additional complexity, and to avoid the confusion with the otherscans. -
I would like to have some safe and simple variant. Like stream and behavior in one thing, what Xstream calls the Memory Stream. So I know that both stream and behavior would conform to this
memoryStreaminterface and I don't have to think which one is which. It may be derived from other more refined versions, but I would like to have it for the sake of convenience. -
A new
MemoryStreaminterface might be a great entry point to build adapters for other libraries as it would accept all streams, promises, behaviours, properties and even observables. So people can use their old code with the api they understand, which is great. -
A new
Pendinginterface could be combined with Streams, Behaviors, or MemoryStreams. It would allow the use the "unlifted" version ofscan, while preserving the purity. -
The
scanfunction for thePendinginterface could be called something like "pendingScan" to emphasise its use case. It would only apply to pending memory streams, in its pure form. Its impure brother can be called "impureScan" and would apply to any memory stream, but with "impure" in it's name, it is no more the library's responsibility :) -
The reducer would gets called and the initialisation time (when the stream is not pending) and when the events are emitted.
Let me know what you think.