mostjs / core

Most.js core event stream
http://mostcore.rtfd.io
MIT License
403 stars 36 forks source link

Clarification on how to observe Event value #253

Closed cortopy closed 6 years ago

cortopy commented 6 years ago

Since https://github.com/mostjs/core/pull/8 I can see methods like observe, reduce, etc. have been removed from core.

I've been reading and trying out some new examples to see how to replace some of the patterns I'd use with those deprecated methods.

I'm not familiar with hyperHTML, but for what I gather a scan transformation is used to trigger side effects in the DOM with hyperHTML's bind function. The same transformation keeps the stream bound to event listeners in the node (a nice one-way cycle!), but it feels odd that the value can only come out as a side effect within a transformation. Is this summary correct?

If so, would it be possible to clarify:

briancavalier commented 6 years ago

Hi @cortopy. Good questions. I'll try to provide a bit of context, and then specifically address your questions. I apologize if you already know some of this, but I like to err on the side of more information.

MostJS event streams are inert until run by calling runEffects. So, side-effects are only performed when demanded by runEffects. In the case of hyperhtml, it was convenient just to use scan. It may be possible with hyperhtml's API to pull that side-effect out of the scan and do it in a tap at the very top level:

const effects = tap(performSideEffects, viewUpdates)
runEffects(effects, scheduler)

or just:

runEffects(tap(performSideEffects, viewUpdates), scheduler)

As you've probably guessed from the example above, runEffects + tap is very much like the original observe. We've considered having a version of runEffects that accepts a side-effecting function, but there hasn't been much demand for it so far. I'm certainly still open to the idea.

it feels odd that the value can only come out as a side effect within a transformation

Caveat: this was the first time I had used hyperhtml. Based on its docs and some examples, it seemed like the idiomatic usage was to call bind inside view functions. In hindsight, it would be nice to pull the usage of bind (the side-effecting call) out of the scan and do that in a final tap, by replacing the bind in view.ts with wire. I may try that to see if it works.

is the intention to trigger side effects within a stream combinator like scan?

It was convenient to use a scan for hyperhtml. runEffects + tap is a more pure/rigorous approach in this particular case.

In my experience, it's not possible or practical in a non-trivial JS app to hoist all side-effects out to "the end of the world" in a final tap. As usual in JS, good judgement and reasonable tradeoffs are required! In practice, the fact that side-effects only execute once demanded with runEffects coupled with good judgement about where to place side-effects has worked well in practice.

how does one get the value out of the stream from either the main or error channel?

I think the "value" part of this question is answered by info above. If not, then it seems I probably misunderstood the question and need a bit more help in understanding it :)

Errors, specifically: the error channel represents stream failure (not application errors), and manifests from runEffects via the returned Promise being rejected. You can use recoverWith to handle stream failures. If you haven't already, it's worth reading the Stream Failure vs. Application Errors docs.

I hope that helps!

briancavalier commented 6 years ago

I just tried the quick and dirty experiment of pulling hyperhtml's dom update side-effect out to the top level. It worked. Here's the branch and the diff if you'd like to check it out.

cortopy commented 6 years ago

Thanks so much @briancavalier. That's very informative and makes it all very clear. I prefer patterns that make side effects explicit and your example above nails it.

I'd just like to add that in regards to:

We've considered having a version of runEffects that accepts a side-effecting function, but there hasn't been much demand for it so far. I'm certainly still open to the idea.

To me runEffects didn't make a lot of sense (probably a matter of semantics), as I could not see what the effects were. After all, even though I can't imagine a use case for it, a stream may be initiated and be pure if no tap is used and no side effects run in the stream.

As an idea for newcomers to Most like me, what about a runEffectsWith that also takes the side effects function and makes it more declarative?

AS A SIDE NOTE:

Are you accepting PRs for the docs? I would like to put something about this there. The examples section points to the repo, but some text there would not hurt

Frikki commented 6 years ago

@cortopy

Are you accepting PRs for the docs?

As most.js is an open source project, we accept PRs for anything related. However, it is good practice to discuss the changes before, to avoid doing unnecessary work.

I would like to put something about this there.

What is this “something” you would like to add?

briancavalier commented 6 years ago

Yes, absolutely, PRs to help improve docs are much appreciated. As @Frikki mentioned, a bit of up front discussion, and/or putting together a small proposal of what you'd like to add can help a lot in heading down a path we all feel is the right one. To that end, feel free to join us in gitter to discuss in more real time, or open an issue or PR to discuss.

cortopy commented 6 years ago

Thanks a lot! I'll open a separate issue to discuss this