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

Side effects and ordering in flyd.on #138

Open
irisjae opened this issue Feb 2, 2017 · 13 comments
Open

Side effects and ordering in flyd.on #138

irisjae opened this issue Feb 2, 2017 · 13 comments

Comments

@irisjae
Copy link

irisjae commented Feb 2, 2017

var x = flyd.stream()
flyd.on(console.log.bind(console,'one'),x)
flyd.on(console.log.bind(console,'two'),x)
flyd.on(console.log.bind(console,'three'),x)
x('fire')

This gives the result

three fire
two fire
one fire

This seems counter intuitive to me and caused some issues for me. Is there any rationale for this behavior? Could we change it? This seems like a major design decision, and if this was intended, I cannot understand the rationale.

@irisjae irisjae changed the title Order of flyd.on Side effects and ordering in flyd.on Feb 2, 2017
@irisjae
Copy link
Author

irisjae commented Feb 2, 2017

Changing this section in updateDeps(s)

for (; orderNextIdx >= 0; --orderNextIdx) {
    o = order[orderNextIdx];
    if (o.shouldUpdate === true) updateStream(o);
    o.queued = false;
  }

to this

  for (var x = 0; x <= orderNextIdx; x++) {
    o = order[x];
    if (o.shouldUpdate === true) updateStream(o);
    o.queued = false;
  }

seems to make it work as expected for me, though I'm not sure if there are any side consequences.

@dmitriz
Copy link
Contributor

dmitriz commented Apr 29, 2017

According to https://github.com/paldepind/flyd#flydonfn-s, the on method is essentially a "worse" version of map as it does the same but does not return anything useful.

And if you use map instead of on, that behaviour becomes intuitive. Mapping over a function f means f is called with every stream value. So when you update your stream, all 3 functions are called. Does it make sense?

@StreetStrider
Copy link
Contributor

@dmitriz, he notes on the order of called functions, not the function itself.

@dmitriz
Copy link
Contributor

dmitriz commented Apr 29, 2017

@StreetStrider Aren't they meant to fire simultaneously?

@StreetStrider
Copy link
Contributor

@dmitriz what do you mean by «simultaneous» here?

@dmitriz
Copy link
Contributor

dmitriz commented Apr 29, 2017

@StreetStrider All at the same time, as soon as their parent emits.

@StreetStrider
Copy link
Contributor

@dmitriz, yes, but this is irrelevant to what TS mentions. He just notes on that on-subscribed functions are called in reverse.

@dmitriz
Copy link
Contributor

dmitriz commented Apr 29, 2017

@StreetStrider The order is not guaranteed by the spec. It may depend on the implementation and on the runtime environment. Why does it matter?

@irisjae
Copy link
Author

irisjae commented Apr 30, 2017

For side effects: clearly it could have great impact

@garkin
Copy link

garkin commented Oct 31, 2017

This is a result of topological sort of the dependency graph. It's needed for atomic updates.

For example in this case sorted nodes array will look like [OnBbb, bbb, OnCc, cc, bb, b, c, d].
Array with sort results should always be iterated in reverse to the build order due the nature of such sort.
This is why you could not change iteration order in the unwrapping loop.

But it is possible to reverse node children iteration order during build.
(both for(of [...listeners]) loops in the updateDeps() and in the findDeps())
In this case you will get following order:

And sorted nodes: [d, OnCc, cc, c, OnBb, bbb, bb, b].
This will result in the sort being sometimes closer to the natural order of children.

@StreetStrider
Copy link
Contributor

Wow, nice picture.

@garkin
Copy link

garkin commented Oct 31, 2017

The problem is a backward compatibilty.
It will change the order of execution for already existing programs.
It would be a breaking change.

If we forget about that - it could be usefull for simple cases. Could be planned for a major version increment.

On the second thought, in complex and dynamic topologies it won't help. Order there won't be obvious from the declaration, and you should not rely on it.

@nordfjord
Copy link
Collaborator

nordfjord commented Jul 12, 2018

This is a quirk of the library as is. Looking at the commit history the reverse iteration is because of a rather nice performance boost.

If you want some b to happen after some a you need to make it explicit by making b depend on a
e.g. for your original example.

const log = a => x => (console.log(a, x), x);
const x = stream();
x.map(log('first')).map(log('second')).map(log('third'));
x('fire');

Now in stead of the dependency graph looking like this:

   x
 / | \
1  2  3

It looks like

x
|
1
|
2
|
3

Meaning things will happen in your preferred order.

I'm not sure this is something we'd like to change, given it's here for perf reasons.

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

5 participants