Comparing core.async and Functional Reactive Programming (+Rx)
Asked Answered
R

5

61

I seem to be a little bit confused when comparing Clojure's core.async to the so called Reactive Extensions (Rx) and FRP in general. They seem to tackle similar problem of async-hronicity, so I wonder what are the principal differences and in what cases is one preferred over the other. Can someone please explain?

EDIT: To encourage more in-depth answers I want to make the question more specific:

  1. Core.async allows me to write synchronous-looking code. However as I understand it FRP only needs one level of nested callbacks (all the function that handle logic are passed as arguments to FRP API). This seems that both approaches make the callback pyramids unnecessary. It is true that in JS I have to write function() {...} many times, but the main problem, the nested callbacks, is gone in FRP also. Do I get it right?

  2. "FRP complects the communication of messages with flow of control" Can you (someone) please give a more specific explanation?

  3. Can't I pass around FRP's observable endpoints the same way as i pass channels?

In general I understand where both approaches historically come from and I have tried few tutorials in both of them. However I seem to be "paralyzed" by the non-obviousness of differences. Is there some example of a code that would be hard to write in one of these and easy using the other? And what is the architectural reason of that?

Rashid answered 17/12, 2013 at 11:7 Comment(2)
It is not good idea to pass observables of observables around.Disquieting
That's not quite true, as it is quite possible, and encouraged for things such as pub/sub, especially with a combineLatest when flattened.Operational
S
32

I think main problem is your assumption about the tackled problem are not quite so, since none of them are tackling the asynchronicity "problem".

The abstractions

FRP main idea is propagation of change, think about accomplishing the same thing Excel does, where you define cells depending on each other in a cascade, and when one cell changes, all the dependent cells on the cascade are recalculated.

core.async main idea is systems decomposition, think as separating concerns using a queue in the middle of different processes, in core.async case instead of queues you have channels but you get the idea.

So, removing the pyramidal code is not the objective of either technology, and they operate on different abstraction layers.

About complecting control flow

The idea about complecting communication and flow control is taken from the original core async post.

While there are various mechanisms to make events/callbacks cleaner (FRP, Rx/Observables) they don't change their fundamental nature, which is that upon an event an arbitrary amount of other code is run, possibly on the same thread, leading to admonitions such as "don't do too much work in your handler", and phrases like "callback hell".

Rephrasing, if you have business domain code inside an event handler, you have complected the X event processing with the what to do when X happens.

Which is what core.async tackles, since introducing a queue/channel in the middle, helps for a better separation of concerns.

Implementation

All your questions regarding callbacks and passing observable endpoints as parameters are just implementation questions, it really depends on the Rx implementation and API.

If you look at React reusable components you really don't have much of a callback hell to be seen, and you get that idea of passing observables around.

Final thoughts

Even if Rx can be used to model any data flow, is more commonly used for UI Rendering a-la Excel, to simplify how you update your view when your model changes.

On the other hand, Core.Async can be used to model separation of concerns when any two sub-systems communicate with each other (same usage scenario as queues), using it on the UI rendering chain main idea is to separate:

  • Server side event generation and processing
  • How that event affects your model

So you can have core.async and FRP together, since core.async will separate concerns, and FRP will define your cascading data-flow once your model is updated.

Sediment answered 26/12, 2013 at 13:42 Comment(1)
CSP can be used to process streams and observables can be used for high-level business logic. I don't see how one or the other is better at separating concerns.Jacklynjackman
C
25

At least one of the main principal differences, and, I think, what Rich ment by "[FRP] complects the communication of messages with flow of control", is the following.

Callbacks are code that is executed. And that execution has to happen in some thread at some point in time. Often, the time is when the event happens, and the thread is the thread that notices/produces the event. If the producer instead put a message on a channel, you are free to consume the message when you want, in whatever thread you want. So callbacks, which are essentially a form of communication, complect the communication with flow of control by dictating when and where the callback code is being executed. If you have to use callbacks for whatever reason, just use them to put a message on a queue/channel and you are back on track.

Because core.async is less complex, it should be preferred as long as there is not a good reason to use the alternatives.

Conversion answered 22/12, 2013 at 5:35 Comment(2)
Rx frameworks have simple APIs (namely, Schedulers) for declaring when and on what thread code should execute. It does not have to be the thread that notices/produces the event.Aristocrat
@Aristocrat OK! I have actually not used Rx, just read about it quickly. I still think what I talked about is what Rich meant.Conversion
R
12

Clojure core.async is a port of go blocks from the Go language. The fundamental concept is channels representing asynchronous communication between threads.

The goal is to be able to write normal looking blocks of sequential code that access channels to get input, and this is seamlessly translated into a state machine that works out the CSP translation for you.

Contrast to FRP, which is still fundamentally about callbacks, and it complects the communication of messages with flow of control. core.async eliminates callbacks from your code entirely, and separates control flow from message transmission. Also, in FRP a channel is not a first class object (ie. you cannot send an FRP channel as a value on an FRP channel).

Rune answered 17/12, 2013 at 17:50 Comment(6)
Thanks for your answer. I have added some more details that I don't understand.Rashid
FRP is not fundamentally about callbacks. callbacks are simply a means to a much higher-order ends. If it were just about callbacks, we'd call it plain old reactive programming.Gantry
Agreed with @christopher-harris that it's not solely about callbacks, as you can get nice compositional behavior without it such as distinctUntilChanged, retry, sample, throttle, etc. There are many operators which require no callback at all. It's more about chaining than anything else.Operational
Additionally, I don't know of any FRP library which forbids you from sending an observable ("FRP channel" in your words) from another observable.Evania
"Also, in FRP a channel is not a first class object (ie. you cannot send an FRP channel as a value on an FRP channel" Since there are no "channels" in Rx frameworks, I'll assume you mean observables/signals. Which is incorrect – you can certainly send observables/signals as values from other observables/signals.Aristocrat
where exactly does it say that core.async is a "port of go blocks from Go"? just curious.Modality
O
5

EDIT:

To answer your questions:

  1. Yes, many times, callbacks can be eliminated, for example with retry logic, distinctUntilChanged and lots of other things such as:

    var obs = getJSON('story.json').retry(3);

    var obs = Rx.Observable.fromEvent(document, 'keyup').distinctUntilChanged();

  2. I'm not sure what that refers to with making it more complex with flow control.

  3. Yes, you can pass around an object like you would a channel, as with the below object.

For example, you can have channel like behavior here using RxJS with a ReplaySubject:

var channel = new Rx.ReplaySubject();

// Send three observables down the chain to subscribe to
channel.onNext(Rx.Observable.timer(0, 250).map(function () { return 1; }));
channel.onNext(Rx.Observable.timer(0, 1000).map(function () { return 2; }));
channel.onNext(Rx.Observable.timer(0, 1500).map(function () { return 3; }));

// Now pass the channel around anywhere!
processChannel(channel);

To make it a little bit more concrete, compare the code from David Nolen's post here with an RxJS FRP example here

Operational answered 18/12, 2013 at 23:33 Comment(2)
I appreciate the thought you put into this, but this is not really an answer to the question.Transfer
Sorry, I was responding to the other one, but didn't have enough rep at the time to do so. Edited to provide a better answer.Operational
H
4

There is a post here that compares FRP with CSP on a limited set of examples (which however meant to demonstrate benefits of CSP), with a conclusion at the end: http://potetm.github.io/2014/01/07/frp.html

Hippel answered 6/3, 2015 at 13:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.