-
Notifications
You must be signed in to change notification settings - Fork 145
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
RxJS-like DOM library #33
Comments
I agree, and I wrote some libraries that do this: https://crates.io/crates/futures-signals We are discussing how deeply Gloo should integrate with Signals (personally, I think we should have Signal-based APIs where it makes sense to do so).
No, they are completely different in their API and behavior. Elm originally tried to conflate them together, but ran into a lot of problems, so ended up splitting them into two types, which is correct. |
(More thoughts on this proposal in general coming soon...)
Do you have any links to elm's discussion of this, the problems they ran into, and how they decided to solve it, etc? |
Not off-hand, it was many years ago. I'll see if I can find anything, but it's possible the links are now lost to the sands of time. On the other hand, I do remember the specific issues they ran into, so I can always explain that. |
After doing a lot of searching, I'm pretty sure the discussions don't exist anymore. They were all the way back in 2014 - 2015. However, I can summarize (some of) the reasons. First, to explain a bit of background: Elm used to have a However, Elm tried to use these Signals in two contradictory ways: to represent a value, and also to represent events. There were some APIs which were intended for only one or the other: for example the If two Signals updated at the same time, then Conversely, there was unexpected behavior when using So even though Elm is statically typed, and even though these APIs both accepted the There are also some performance implications: because Signals were used for events, that means they must never drop updates. And it also means that updates must always occur in the correct order. That's all well and good for events, but values do not need those restrictions, and so it's possible to implement values more efficiently (by dropping intermediate updates, by delaying updates, by re-ordering updates, etc.) So by forcing Signals to act as both events and values, that caused the values to have an unnecessary performance cost. As far as Rust is concerned, that basically equates to having an extra In addition, there were some fundamental API restrictions placed upon Signals, and those restrictions existed precisely because Elm was trying to treat Signals as both values and events at the same time: https://youtu.be/Agu6jipKfYw?t=804 (it gives a good overview of FRP in general, I recommend watching the full video) Basically, because But dynamically changing the Signal graph is a completely reasonable (and useful!) thing to do, so this restriction isn't great. All of these issues were solved by splitting Signals into two types: Stream and Varying. Stream is used for events, and Varying is used for values. Now it's straightforward to only define In addition, the To use a Rust analogy, Elm's Varying is similar to I took a look at the most recent version of Elm, and as far as I can see they've completely abandoned FRP, now they essentially use some sort of event subscription + effect system. So everything I mentioned is just for historical reference. |
Here are some relevant (very old) discussions from Elm that I found: https://groups.google.com/d/msg/elm-discuss/w2Rmim4IUn4/pVOZlvZGTqoJ Keep in mind the above posts are generally discussing the old Signal system, before the split. None of them really go into why Elm made the split, or what exactly the split was, but they do provide some interesting background. The post by John Mayer is particularly good, and clearly explains what the difference is between a Stream and a Varying (e.g. mouse click vs mouse position). I also wrote a detailed GitHub comment which explains some of the design decisions I made with my Pauan/rust-signals#1 (comment) I've spent the past several years researching (and using in practice) various different FRP systems, so I have a good amount of knowledge about the different designs and trade-offs. I'm happy to discuss more, if anybody is interested or has any questions. |
Thank you for all the links / info, I have quite a bit of reading to do. :)
Since (You are probably correct here as you are much more knowledgable about this than I am, but since it seems that streams are destined to become a core rust language abstraction for any asynchronous series of values, I feel that we should be absolutely certain that these differences are irreconcilable with the
I had not realized this, that's disappointing. |
Blog post explaining why Elm abandoned FRP: https://elm-lang.org/blog/farewell-to-frp In short, they found a simpler, easier to learn design that did what was needed. |
Thanks for all the context, everyone. I enjoyed reading all that and watching Evan's talk. I think it eventually makes sense to have a I have a few concerns that I think we should address before we actually do this:
Put all these things together and I think this is a reasonable plan for the near term: build an FRP/signals-based framework/library on top of Gloo outside of this repository, before we attempt adding What do y'all think? |
Unfortunately, they cannot. The actual methods are defined on A lot of APIs behave completely differently between
It's an API issue, not an implementation issue, so merely changing the implementation doesn't solve the problem.
If you only look at the type of A Signal has a contract which requires it to always have a current value. Thus when a Signal is first polled, it must return This requirement doesn't exist for Streams, which might not have any values at all (i.e. they might be currently empty). There is also the fact that Streams are generally expected to contain all values in the correct order (unless you explicitly use an API which drops values), whereas Signals are expected to drop values whenever they want (as long as they don't drop the current value). This has huge implications for performance and preventing bugs. Also, the primary reason why In other words, it's the same reason people use newtypes in Haskell/Rust: even though the underlying type might be the same, the API/behavior is different. As an example, you might have two types: So the fact that the internal type of Stream/Signal is similar does not mean that they can be unified (just like how
Indeed, Streams work great for that! But a Signal is not an asynchronous series of values. The way to think of it is that a Signal is a mutable variable that notifies you when it changes, whereas a Stream is an asynchronous Naturally their behavior is rather different, even if the underlying types might seem similar.
I'm never 100% sure of anything, but I'm 99.999% sure that they are incompatible in their implementation, in their API, and conceptually/mathematically. There's a reason why FRP splits things into Also, I don't see it as "committing" to anything. I have always advocated for using Some web APIs might even make sense for both In the same way that some APIs might make sense for both
If I tried to do that, then:
This would break the Signals contract, and unleash all the issues that Elm ran into. There's no way to "remove" extension methods, you can only add extension methods. But correct behavior of Stream/Signal requires the removal of APIs. P.S. Thank you for asking all these questions! It will help me to write up a good explanation of why Signals are useful (and why we can't just use Streams). |
I'm really not excited about forking Signals to use Futures 0.1 (I already spent a lot of effort moving from Futures 0.2 to Futures 0.3) Because Futures 0.3 provides some compat shims which allows it to work with Futures 0.1, I believe it should be possible to use those shims to use If those compat shims don't work, I'm okay with waiting for Gloo to move to Futures 0.3
This is a good question! The most recent breaking change was in November 2018, and it was an extremely small breaking change that only affected one very specific API (the commit looks big, but the breaking change itself is very small). The core has not changed in a long time. I'm pretty confident that the API and implementation of Naturally if a breaking change occurs, it will use semver (as usual). And once
Yes, this bothers me a lot as well! I would like to have more contributors/maintainers. I've also considered moving
That all sounds great to me! |
Hi,
I think it's worth to mention that I am doing an experiment with @Pauan's |
Summary
RxJS is "a library for composing asynchronous and event-based programs by using observable sequences." Its core abstraction is the "observable", which is a collection of future values that has many commonalities with rust streams. I propose to create a similar API for gloo based on streams.
Motivation
gloo
needs a library for interacting with DOM events that works well within the idioms of rust. The patterns inRxJS
might provide a good fit. Basing it off of streams takes advantage of rust's strong async story, and will only become more ergonomic in the future with features such as async/await and language-level support for streams.Detailed Explanation
A wrapper type would be created for each element, with a
Stream
-returning method for each event that the element can emit.For example, the RxJS smart counter example (which implements a simple odometer effect) might look something like this:
(I prototyped just enough to get this working here)
Drawbacks, Rationale, and Alternatives
Since this would just be another crate in
gloo
, I think the main alternative is to just not do this.The major drawback is that this API is not as ergonomic as it could be with rust as it is today, but future language features might change that.
Unresolved Questions
RxRust
project (that does not use streams IIRC) - does this overlap?The text was updated successfully, but these errors were encountered: