3 ms·
I don't fully understand the argument against FRP here. It seems like the author has passed on FRP in favor of coroutine-like asynchronous programming. I don'
by davexunit 11y ago
I don't fully understand the argument against FRP here. It seems like the author has passed on FRP in favor of coroutine-like asynchronous programming. I don't view one as replacement for the other, but as different layers of an asynchronous programming system. Coroutines are great for async and imperative tasks, while FRP is great for async and functional tasks with persistent data. My own FRP implementation [0] in Guile Scheme is built on top of a coroutine implementation [1], which in turn is built on top of Guile's first-class delimited continuations. [2]
That said, the author specifically talks about RxJs and Bacon.js, both of which have two major problems for me:
1) They don't satisfy the closure property. They both have two fundamental data types: event streams and properties. Certain combinators expect streams, and others expect properties. Compare this with API's like Elm's which just have a single type: the signal. The closure property is satisfied here and programming is much more pleasant.
2) They both deal with stream/property life cycles. Objects need to explicitly unsubscribe from other objects, and streams may have a beginning and an end (i.e. they may be marked as having no value or marked as being done producing values). I think this is a mistake that complicates the API. FRP objects should always have a value, have no notion of being done, and not require the equivalent of manual memory management to clean up. My Scheme implementation uses weak references to automatically unsubscribe signals when they are no longer referenced, which is basically only during development when changing things at the REPL. Bacon and RxJS can't do something like this because (and correct me if I'm wrong), no JS standard prior to ES6 has weak data structures. Anyway, after all the hacking, the final program has a static signal graph, just like Elm, which I think is the right way to do things.
[0] https://git.dthompson.us/sly.git/blob/HEAD:/sly/signal.scm https://git.dthompson.us/sly.git/blob/HEAD:/sly/signal.scm
[1] https://git.dthompson.us/sly.git/blob/HEAD:/sly/coroutine.scm https://git.dthompson.us/sly.git/blob/HEAD:/sly/coroutine.sc...
[2] https://www.gnu.org/software/guile/manual/html_node/Prompts.html https://www.gnu.org/software/guile/manual/html_node/Prompts....
- WorldMaker 11y agoRxJS will automatically dispose/unsubscribe in composition scenarios: for instance, if you flatMap from one "underlying" observable to a sequence of observables, when you unsubscribe from the "underlying" observable it will observe any remaining (if any) hot observables that were kicked off by the flatMap. RxJS also automatically disposes resources when an observable completes and maybe that's a part of what you are missing in the observable lifecycle and part of why you've felt that RxJS has an equivalent of "manual memory management"? Yes, lots of streams are effectively infinite in nature, but that doesn't mean that all of them are, and a completion signal can still be useful and informative, including in lifecycle management. (It also helps keep the duality between Enumerable/Iterator worlds and Observable/Observer worlds.) One obvious case that I see a lot in applications where finite streams/observables show up in great number is that Promise (Task/Future) is essentially an observable that produces one result and completes.
- davexunit 11y ago>RxJS will automatically dispose/unsubscribe in composition scenarios But it still requires manually unsubscribing from something in the first place. I just think that's the wrong approach to FRP. The graphs are actually static, but dynamic behavior is needed for developing at the REPL.
- WorldMaker 11y agoThus far I've only had to manually dispose a single RxJS observable, and that turned out to lead me to a better composition strategy and I got rid of the manual dispose. If I'm working in a REPL I set things up to "naturally complete" with some useful sample set, just as if I were working with potentially infinite enumerables. In RxJS that would typically be something like myObservable.take(5).