Hacker News new | past | comments | ask | show | jobs | submit login

Can we please stop using the term reactive programming wrong? Reactive programming[1], aka dataflow programming, is writing programs that work like a spreadsheet. Microsoft Excel is a great example of an environment for reactive programming. Functional reactive programming, or FRP, is simply reactive programing in a functional style.

What we have here isn't reactive programming at all, but a few patterns (or, rather, anti-patterns) devised to avoid blocking kernel threads (only some of them are related to reactive programming). They manage to avoid blocking but at great cost. If the overhead caused by blocking bothers you, you need to understand why and realize there are far better ways of avoiding it than these anti-patterns.

[1]: http://en.wikipedia.org/wiki/Reactive_programming




> Functional reactive programming, or FRP, is simply reactive programing in a functional style.

Functional Reactive Programming is a programming model invented by Paul Hudak and Conal Elliott. As Conal says on C2. There are other reactive programming systems that are functional but not FRP.

Also note that people often use the term "reactive programming" to mean "the programming of reactive applications." Obviously, data flow doesn't have to be used to make something reactive, which is a property of the artifact and does not depend on how it was built.


> there are far better ways of avoiding it than these anti-patterns.

Could you describe or at least point to some of them?


Lightweight threads. They make blocking free, and allow using constructs far more suitable for imperative languages. They don't destroy your stack traces, and don't require you to re-invent exception handling and control flow. Instead of promises -- simple blocking calls (or blocking futures in some cases); instead of observables -- blocking queues (aka channels).

Instead of reaching out for ways to avoid blocking, we just make blocking free.


Do you mean co-routines / user threads / green threads? I tend to agree it can have a serious performance boost in some cases. Not sure why you were down voted.

There is actually a library for java that adds it via byte code instrumentation (quasar or something) although not sure it will work for scala.

But saying that actor model is bad practice, I'm not sure that I accept it. Maybe on a single muticore but once you start talking distributed computing (e.g. Spark which is Akka based) then all this "avoid crossing to the kernel" optimization is becoming a drop in the ocean.


> all this "avoid crossing to the kernel" optimization is becoming a drop in the ocean

Here is an example of a small single threaded program beating out a number of distributed graph frameworks running on 128 cores, with a 128 billion edge dataset.

http://www.frankmcsherry.org/graph/scalability/cost/2015/02/...

Performance matters because it enables simplicity. If your language forces you to pull in multiple machines to solve your problem then its turned a simple program into a distributed system and life gets complicated fast. Just throwing more cores at a program without understanding why its slow will just get you into trouble.

Multithreaded programs and distributed programs should be a scary last resort after making absolutely sure you can't get away with the simple solution.


Yes I saw this, and got a little disillusioned at first, but after looking carefully this is not big data, their entire dataset fits in RAM. When your dataset can't fit in RAM - this is where the last resort comes into play. Sadly most companies, I agree, don't know when data is really big data. Most of the time it's just medium data. And I agree about the overhead costs.


> their entire dataset fits in RAM

128 billion edges. 1 TB of data just to list edges as pairs of integers. 154 GB after cleverly encoding edges as variable length offsets in a Hilbert curve.

Do you have a bigger dataset?


Oh, I was referring to the original posts. Will take a look. Thanks!


> Do you mean co-routines / user threads / green threads? I tend to agree it can have a serious performance boost in some cases.

Yes, but in this context, they provide the same performance benefits as the asynchronous techniques mentioned in the article, without all the drawbacks of the cumbersome asynchronous style.

> But saying that actor model is bad practice

The actor model is a general technique for fault-tolerance, and it's great. How it handles concurrency, though, is an orthogonal concern. Akka has asynchronous (callback based) actors, and callbacks are an anti-pattern. Erlang has synchronous (blocking) actors, which are so much simpler, and don't have all the complications associated with asynchronous code.


Erlang has asynchronous message passing by default, on which synchronous message passing can be built.


I would agree that in both Akka and Erlang message passing works in an asynchronous way.

For me the difference is more that Akka has a push-style approach to message processing in the receiver (the receive message is automatically called) whereas Erlang and also e.g. the F# Mailboxprocessor use a pull-style appproach (you call receive to fetch a message). I like the latter approach better, because it allows one to start also different asynchronous operations which won't be interrupted by the reception of a new message.


That's not what I meant. Message receive in Erlang is blocking (synchronous), just like it is in Go and Quasar. Meaning, it's basically a blocking queue rather than the asynchronous/push-style/callback approach used in Akka.


Why are you being downvoted for a constructive comment?

"Free blocking" was what attracted me to Erlang's actors in green threads as opposed to Akka's actors that block entire threads when they block.


Yes. And in fact we can even use kernel threads most of the time.

However, there is one thread you don't want to block: the UI thread. We do need ways of getting things to the UI asynchronously.


> And in fact we can even use kernel threads most of the time.

Well, not if you need tens-of-thousands of them or more.

> However, there is one thread you don't want to block: the UI thread.

No problem. You simply multiplex fibers onto the UI thread, where they can block all they want. Here's an example in Quasar (Java):

    FiberScheduler uiScheduler = new FiberExecutorScheduler("UI-scheduler", 
       task -> EventQueue.invokeLater(task)); // schedule on the UI thread

    new Fiber(uiScheduler, () -> {
            for (i = 0;; i++) {
                assert EventQueue.isDispatchThread(); // I'm on the UI thread!
                uiLabel.setText("num: " + i); // I can update the ui
                Fiber.sleep(1000); // ... yet I can block all I want
            }
        }).start();


"most of the time" we don't need tens-of-thousands of threads.

Yes, I can easily dispatch something onto the UI thread using the technique you describe, but for that using a simple HOM is much more convenient:

   uiLabel onMainThread setText:'num: ', i.
Neither this nor your example are actually blocking the UI thread, because they are really just incidentally there and just pushing data in, a quick in/out. (And I assume that "Fiber.sleep()" takes the Fiber off the main thread )

However, the more difficult part is if the UI thread actually has control flow and data dependencies, let's say a table view that is requesting data lazily. Taking the blocking computation off the UI thread doesn't work there, because the return value is needed by UI thread to continue.


> "most of the time" we don't need tens-of-thousands of threads.

Well, that depends what you mean by "most of the time". But if you have a server that served tens-of-thousands of concurrent requests/sessions, you'd want to use as many threads as sessions, and probably many more (as each request might fan out to more requests executing in parallel). In that case you can no longer use kernel threads and have two options: switch to an asynchronous programming style, with all its problems (in imperative languages), or keep your code simple, and simply switch from using kernel threads to lightweight threads.

> However, the more difficult part is if the UI thread actually has control flow and data dependencies, let's say a table view that is requesting data lazily. Taking the blocking computation off the UI thread doesn't work there, because the return value is needed by UI thread to continue.

Again, that's not a problem. If you use lightweight threads scheduled onto the UI thread, you can block those fibers as much as you like -- synchronously read from the disk or a database -- the kernel UI thread doesn't notice it (as it's not really blocked), but from the programmer's perspective, you can intersperse UI and blocking operations (including IO) all you want.


> Well, that depends what you mean by "most of the time".

It means "most of the time". As in the majority of cases, in the real world, not in hypotheticals such as...

> But if you have a server that served tens-of-thousands of concurrent requests/sessions,

"If you have..." -- But I do not, that's the point. A server with tens-of-thousands of concurrent requests is the absolute exception and so not "most of the time". Most web-sites or blogs can be happy if they have a thousand visitors per day, and that's already optimistic. They could be served by hand, or even by a Rails app.

For example, I work for Wunderlist (Alexa rank ~1600). We have over 10 million users (of our API), so already an unusual high-load case, yet we get "only" on the order of 10K requests per minute. (Well, that was last summer, so more now :-) )

Considering that most requests take a couple or maybe dozens of milliseconds, the amount of actual concurrency required to handle this throughput is orders of magnitude below what you describe. In order to keep latencies down and not just throughput up, you want to up the concurrency a bit, but to nowhere near your "but if" case. And that's an app with 10 million very active users. The case you describe is simply highly atypical. That doesn't mean it never happens, it's just not very common, even on the server.

That's what I mean when I write "most of the time".

Clients, on the other hand, tend to deal at most with on the order of 100 outstanding I/O requests (that's already pushing it pretty hard). Whether you use kernel threads, user threads or another of these async mechanisms is almost entirely a matter of programmer convenience, performance will be largely indistinguishable. On the client, I have a hard time seeing your case pretty much ever.

So you have none of the clients and a tiny percentage of servers with the need for 10s of thousands of concurrent requests. The other case is what happens "most of the time". That also doesn't mean you can't use a user-thread approach in those cases, you certainly can, it's just not necessary.

---

I am not sure I am getting through to you with the UI thread. One more try: yes, I understand you can reschedule your fibers (and thus not block the UI thread). I am saying it doesn't help, because you have an actual control flow and data dependencies that are essential, they are not artifacts of technology.

Scenario: You have an iPhone app that displays flickr images. You start the app, there are no images yet, they have to be fetched. But you UICollectionView just came into focus and is asking you, the data source, for the Subviews. You know that there are 10 images, so you tell it that. It then asks you for the the visible subviews. At this point, you have to return something, because the collection view wants to display something. But you don't have the image yet, it's still in transit. Still the UI has to display something to the user. So you can return empty UIImageViews. Or you can return placeholder views.

No matter what you do, you have to do it now as the UI is being displayed, because you can't de-schedule the user that is looking at the screen.

And later, when those images do trickle in from the network connection, you have to somehow asynchronously update that UI you displayed earlier. You simply cannot do it synchronously because at the time the display is built, the data just isn't there yet.


> That's what I mean when I write "most of the time".

I absolutely agree that in the scenarios you've described, the thread implementation makes no difference (again Little's Law), and that this is "most of the time" for traditional web apps. But we're working with people who are working on IoT, where there is constant ingest (and queries) from all sorts of devices, where we're easily surpassing 100K concurrent request, and expected to grow beyond that by several orders of magnitude.

> No matter what you do, you have to do it now as the UI is being displayed, because you can't de-schedule the user that is looking at the screen.

Ah, now I understand what you mean (thanks for being patient)! Still, I think this pseudo code (which gets run in a fiber for every image):

   display stub image
   fetch image
   display image
is easier than any callback-based alternative. You can even make it more interesting:

   Future<Image> image = get image
   while(!image.isDone) {
       display spinner frame
       sleep 20 ms
   }
   display image
This is a lot easier than the async alternative.


By "tens-of-thousands of threads" I think he means something along the lines of how in Erlang/Elixir an object is often a thread, and a library a program. By giving so many threads "for free" you make blocking cost nothing. It's a very different approach from your typical language.

This article only uses a few threads, but it will perhaps quickly give you an impression of how this design works: https://howistart.org/posts/elixir/1


I am fully aware of the approach, especially in languages/systems like Erlang, and the freedom that very cheap threads give you.

My first point was that you actually have much more of this freedom than most people are aware of, even with (comparatively) heavy kernel threads. For example, see the "Replace user threads with ... threads" by Paul Turned (Plumber's Conference): https://www.youtube.com/watch?v=KXuZi9aeGTw

More on that point, I see a lot of user-threading/async craziness on clients such as iOS that would have been easily been handled by less than a dozen kernel threads, most of which would be sleeping most of the time anyhow. That's a number of kernel threads that is easily manageable and not particularly resource intensive.

My second point is that there is one thread that this blocking-happy approach mostly doesn't apply to, and that is the UI thread. You really don't want that UI waiting for (network) I/O and therefore must employ some sort of asynchronous mechanism for data flow and/or notifications.


The Paul Turner approach works when you have up to about 10K-20K threads. Beyond that, you lose the ability to model a domain unit-of-concurrency (request/session) as a software unit of concurrency (thread). The kernel-thread approach works as long as you don't hit against the Little's Law wall. Basically, Little's Law tells you exactly when kernel threads become the wrong approach, which depends on the level of concurrency you wish to support and the mean latency of handling each concurrency unit (i.e. request/session).

> My second point is that there is one thread that this blocking-happy approach mostly doesn't apply to, and that is the UI thread.

You're not allowed to block the kernel UI thread, but you can schedule lightweight threads onto the UI thread and block them all you want, so from the programmer's perspective that restriction disappears.


Sorry, I was just trying to help clarify what was being said -- not trying to argue against your points.


So, the actor model is an anti-pattern? Like Akka in Scala?


See my reply to eranation


Like C# (and now ES7's) async/await?


Not quite. async/await are what's known as stackless coroutines, and require explicit usage. Lightweight threads, aka user mode threads, aka fibers, are simply threads that have negligible (or no) cost associated with blocking. Examples include Erlang processes, Go goroutines and Quasar fibers.


It doesn't matter what it really means , it's like isomorphic applications , and stuff like that.A few Hipsters decided it was a good buzzword to sell this or that framework or solution , and will trash the rest. It's marketing. It's unfortunate but it is how it works. Most devs will never know what it really means, they will just think "this or that framework" when they hear reactive programming.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: