Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[question] Differences in behaviour of flattened stream #233

Open
jeron-diovis opened this issue Feb 3, 2017 · 6 comments
Open

[question] Differences in behaviour of flattened stream #233

jeron-diovis opened this issue Feb 3, 2017 · 6 comments

Comments

@jeron-diovis
Copy link

jeron-diovis commented Feb 3, 2017

Hi!
It found yet another interesting case with .constant method (my favourite one 😄), which I can't explain myself, so I need a help.

A simple snippet:

Kefir.sequentially(100, [ [1, 2, 3] ])
.flatten()
.spy("\ninput")
.scan((a, b) => {
  console.log("[scan] %s + %s", a, b);
  return a + b;
}, 0)
.changes()
.log("output")

Output is pretty predictable:

input <value> 1
[scan] 0 + 1
output <value> 1

input <value> 2
[scan] 1 + 2
output <value> 3

input <value> 3
[scan] 3 + 3
output <value> 6

input <end>
output <end>

Now let's do a little change:

Kefir.constant([1, 2, 3])
.flatten()
.spy("\ninput")
.scan((a, b) => {
  console.log("[scan] %s + %s", a, b);
  return a + b;
}, 0)
.changes()
.log("output")

Output:

input <value> 1
[scan] 0 + 1

input <value> 2
[scan] 1 + 2

input <value> 3
[scan] 3 + 3

input <end>
output <end:current>

It looks quite confusing. Of course, there is a difference – constant creates a property, while sequentially creates a stream. But I can't understand how does it matter in this case. From naive point of view, these two snippets are logically equivalent: they both emit one array value and then end; and in both cases array is then synchronously transformed into series of separate values.

But in case with constant, it all looks like we're still dealing with a single value in the entire chain – note that final sum is not emitted. Even more, if we add .changes() call after .flatten(), then logger only outputs <end> – just like if we've skipped the current value of property. Despite doc says that flatten always creates a stream, and so, if I understand correctly, .changes should do effectively nothing here.

Is it expected behaviour? And if yes, what logic is behind it?

@jensklose
Copy link

Yes of course.

The logic is that the differences in behaviour came from changes. You subscribe only the default from a property. First change is the "end".

@mAAdhaTTah
Copy link
Collaborator

they both emit one array value and then end

Not exactly; the constant Observable is created with the value already in the stream, whereas the sequentially Observable emits the value into the stream. This is why changes skips the value.

@jeron-diovis
Copy link
Author

jeron-diovis commented Feb 4, 2017

the constant Observable is created with the value already in the stream

You subscribe only the default from a property. First change is the "end"

I thought than flatten will neutralize the difference then. No matter whether array was emitted or it was initially there – after flatten we always deal with a stream which emits 3 times, and it's value changes 3 times, isn't it? It's 3 separate events, how can they be considered as a single one?

@jensklose
Copy link

jensklose commented Feb 5, 2017

It isn't.

Kefir.constant([1, 2, 3])
  .flatten()
  .spy()
  .log('observer');

Output:

[constant.flatten] <value> 1
observer <value:current> 1
[constant.flatten] <value> 2
observer <value:current> 2
[constant.flatten] <value> 3
observer <value:current> 3
[constant.flatten] <end>
observer <end:current>

The spy logger don't add currentstate. Nevertheless he is there.
So your scan property does what you want, but your changes filters all the current values.

It's a strange example and I should thought about my use of constant. It isn't an event pusher, it is a property holder.

To init a stream you can:

Kefir.later(0, [1, 2, 3])
  .flatten()

@jeron-diovis
Copy link
Author

@jensklose this is what I'm asking about. Why it works like this?

I don't think example using constant is strange. Let say I'm writing a test for some stream transformation function (where all transformations are synchronous). I need a stub for it. And then why should I use .later(0, ...) and bring asynchrony in my completely synchronous logic? Instead of just creating a stream with value already there?

It is counter-intuitive as for library user, this what I'm trying to say. constant is something so special that you never can rely on it. For example, .delay() isn't applied to it (at least, this is explicitly noted in docs). Just because. While expected behaviour is https://jsfiddle.net/ryoeghxf/4/.

I want to know, is it a, let say, "side-effect" of library design (and so, ok, we should live with this, at least for now), or all this is intentional (and then what's the real practical purpose) ?

@jensklose
Copy link

First of all, it's intentional. And yes for a library user it is important to know the difference of streams and properties.

Your mock should work with constant. But you should avoid the usage of changes because the documentation says:

Converts a property to a stream. If the property has a current value (or error), it will be ignored (subscribers of the stream won't get it).

What's the real practical purpose?

Properties are setting observable values. You could pull at any time (synchronously) a library compatible current value.
Streams are sequences of events. They aren't synchronously by nature. The library gives you the means to evaluate it imperatively. The events are uniquely and irrevocably bound to their generation in the context of the universe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants