Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

It looks like we are going to keep reinventing dataflow constraints[1] over and over again, always with slightly different terminology (Rx, FRP, signals, ...)

So it (a) appears to be a very useful or at least attractive concept, and (b) somehow difficult to fit into current programming languages/practice in a clean way.

[1] https://blog.metaobject.com/2014/03/the-siren-call-of-kvo-an... (HN: https://news.ycombinator.com/item?id=7404149 )



Surprisingly, FRP doesn't have anything to do with dataflow constraints at all.

In FRP, a program is fundamentally a function of type Stream Input → Stream Output. That is, a program transforms a stream of inputs into a stream of outputs. If you think about this a bit more, you realise that any implementable function has to be one whose first k outputs are determined by at most the first k inputs -- i.e., you can't look into the future. That is, these functions have to be causal.

The causality constraint implies (with a not-entirely trivial proof) that every causal stream function is equivalent to a state machine (and vice-versa) -- i.e., a current state s, and an update function f : State × Input → State × Output. You get the stream by using the update function to produce a new state and an output in response to each input. (This is an infinite-state Mealy machine for the experts.)

Note that there is no dataflow here: it's just an ordinary state machine. As a result, the GUI paradigm that traditional FRP lends itself to the best are immediate mode GUIs. (FRP can be extended to handle asynchronous events, but doing so in a way that has the right performance model is not trivial. Think about how you'd mix immediate and retained mode to get an idea about the issues.)

When I first started working on FRP I thought it had to be dataflow -- my first papers on it are actually about signals libraries like the one in the post. However, I learned that basing it on dataflow and/or incremental computation was both unnecessary and expensive. IMO, we should save that for when we really need it, but shouldn't use it by default.


A couple of notes:

1. You seem to be confusing "dataflow constraints" with "dataflow". Though related, they are not the same.

2. Yes, the implementation of Rx-style "FRP" (should have used the scare quotes to indicate I am referring to the common usage, not actual FRP as defined by Conal Elliott) has deviated. And has deviated before. This also happened with Lucid.

3. However, the question is which of the two is the unnecessary bit. As far as I can tell, what people actually want from this is "it should work like a spreadsheet", so dataflow constraints (also known as spreadsheet constraints). This is also how people understand when used practically. And of course dataflow is also where all this Rx stuff came from (see Messerschmitt's synchronous dataflow)

4. Yes, the synchronous dataflow languages Lustre and Esterel apparently can be and routinely are compiled to state machines. In fact, if I understood the papers correctly the synchronous dataflow languages are seen as a convenient way to specify state machines.

5. It would probably help if you added some links to your papers.


Also, as someone who both works on open source reactive stuff (like MobX) and literally works on a spreadsheet app (Excel) I can say what we do is entirely different from what these systems do because of different constraints and both are reactive.


I'm curious. How so?


In a gist: MobX just keeps the whole dependency graph of what data needs to update when what computed runs. Excel can't do that because it has to be able to work on large files with impartial data.


Aren't excel formulas the expression of data dependency graphs?

In which case it is kept in the document?


Isn't the Excel document still in memory?


Mathematically, RxJS and Vue/Solid Qwik like FRP are equivalent. There is interesting proof by Meijer (who invented Rx) in the famous "duality and the end of reactive". https://www.youtube.com/watch?v=SVYGmGYXLpY


> Mathematically, RxJS and Vue/Solid Qwik like FRP are equivalent.

Yes, these are basically the same thing. However, they have little to do with Conal Elliott's "FRP". Which itself is also badly named.

> Meijer (who invented Rx)

Well "invented". What's a bit surprising is that with both Rx and the Rx-style "FRP", there either is no public history of the ideas at all (Rx) or it is patently wrong (Rx-style "FRP"). Or a bit of both.

For example, the "Reactive" part of Rx-style FRP appears to come from the definition of system styles by Harel. Both the connection of synchronous dataflow with the term "Reactive" and with FP languages are made in the paper on Lustre, which is a language that integrates synchronous dataflow into an FP language. But there is nothing inherently FP-ish about synchronous dataflow, it was previously integrated into to the imperative language Esterel, and they also made a variant of C with synchronous dataflow.

Again, nobody mentions this, it is all presented as having been invented out of thin air and the principles of Functional Programming. (Or as having come from Conal Elliott's FRP, which is not true. Ask Conal Elliott).

Once I figured out the connections, I asked Erik Meijer, who has "I am the original inventor of Rx..." in his bio. He admitted that he was "inspired" by synchronous dataflow. And of course that is pretty much all it is. Except they dropped the requirement for it to be synchronous.

What do you get when you drop "synchronous" from "synchronous dataflow"? FRP, obviously ;-)

Just like Objective-C is Smalltalk + C, and Objective-C - C is ... Swift?

Controlling narratives is important.


All this is documented in Meijer's actual paper though? (Your mouse is a database) as well as his aformentioned talk (duality and the end of reactive).

He for sure invented observables (as we know it and as the mathematically dual of enumerables) - that doesn't mean it was the first ever reactive system or the concept of data being dependent of other data.

If I remember correctly he also explicitly talks abut Conal FRP and notations in https://www.youtube.com/watch?v=pOl4E8x3fmw if interested


I have seen plenty of Erik's talks and certainly read Your mouse is a database a bunch of times.

What is "documented" there is the alternate history of all this coming out of fundamental insights into FP and dualities and all that.

But that isn't the case, it just sounds a lot better than "I found a really complicated way to map dataflow onto FP concepts".

But we as an industry just love complicated.


I think the important contribution of Meijer and RX is realizing the duality of IEnumerable and IObservable which enabled them to use the same programming constructs. The meat of RX is in all the combinators so you can express complex behaviours very succinctly.


Hmm...Smalltalk had the same iteration methods over streams and collections since at least Smalltalk-80, so not sure what the important contribution here is.

(Waiting for the LISPers to chime in...)


An observable is not the same as a Smalltalk stream, directionality wise. Smalltalk streams are like unix streams or a C stream.


Whether the flow is push or pull is a fairly irrelevant implementation detail for a dataflow system.

That doesn't mean it can't have big effects, but if you're relying on those, or they do become relevant, you should probably ask yourself whether your system is really dataflow.


> Note that there is no dataflow here: it's just an ordinary state machine.

It sounds like you've converted data-flow to its state-space form. It's still data flow, just in a variant that might be easier to compute.

FWIW you probably need a pair of functions :

    next state = F(input, current state)  
    output     = G(input, current state)
Which in the signal-processing/control systems world is

    s = Ax + Bs
    y = Cx + Ds
Aka the "state space" formulation where A, B, C, and D are matrices, x is the input, s is the state, and y is the output. There are infinite ways to formulate the state space and infinite equivalent signal flow graphs that represent the same thing.


> any implementable function has to be one whose first k outputs are determined by at most the first k inputs -- i.e., you can't look into the future. That is, these functions have to be causal.

    def f(input_stream):
        i = next(input_stream)
        j = next(input_stream)
        yield i + j
        f(input_stream)
This function produces k outputs when given 2*k inputs, so it's either acausal or impossible to execute. Right?


For GP’s stream-transformer / state-machine equivalence to work, you need to sprinkle option types throughout so each input yields some kind of output, even if empty. So more like

  def co():
      i = yield None # hurray for off-by-one streams
      j = yield None
      while True:
          i = yield i + j
          j = yield None
This won’t help if the output stream produces more than one output item from each input item. You could sprinkle lists instead, but in reality multiple simultaneous events have always been a sore point for FRP—in some libraries you can have them (and that’s awkward in some cases), in some you can’t (and that’s awkward in others).


But I am not criticizing his stream-transformer / state-machine equivalence, I am just curious why he thinks functions of Stream A -> Stream B have to produce exactly 1 output for exactly 1 input.

Now, I know that Haskell in its pre-monad days used to have main have signature [Response] -> [Request]: the lists being lazy, they're essentially streams. Each Request produced by the main would result in a Response being provided to it by the runtime. This model actually has to be strictly 1-to-1, and indeed, it was so easy to accidentally deadlock yourself that switching to IO monad was quite welcomed, according to SPJ in his "Tackling the Awkward Squad" paper.


I guess what I wanted to say was that to me (given that the comment was presumably targeted at people who already know how all of FRP, stream transformers, and state transducers work, or can at least make a good guess) it was within the limits of acceptable sloppiness to mix up (State, Input) -> (State, Output), (State, Input) -> (State, Maybe Output), and (State, Input) -> (State, [Output]), or the equivalent on the stream transformation side. The point of the comment does not really depend on what you choose here.


someone calls conal elliott


A language/framework that explored well this idea is Elm, which is used in front-end apps. It started with Signals and FRP, then 7 years ago changed course to the Model-View Update architecture [1], today known as The Elm Architecture. It is still a bit niche, but a handful of frameworks in other languages like Rust are following this architecture for GUI apps.

[1] https://elm-lang.org/news/farewell-to-frp


The idea in and of itself is not what is being rediscovered. Nobody is reinventing the wheel. It's whether the implementation is ergonomic and powerful that determines whether it catches on.


Signals have become enough of a buzz word that they may catch on just for the hype, regardless of whether they are particularly powerful or ergonomic.


I feel like signals have been a buzzword for almost 10 years now.


May just be my memory failing me or the circles I ran in over the years, but I remember the related buzzwords always being observables and streams. I hadn't seen much interest/hype in the actual term "signal" until solidjs used it and push heavily on comparisons with react hooks.


"Signal" has been used in FRP circles for some time [1,2]. The original FRP stuff was events/signals and behaviours. But I agree that JS didn't use this terminology until more recently. S.js is maybe one of the earlier ones, but that was still over 8 years ago.

[1] https://scholarworks.rit.edu/cgi/viewcontent.cgi?article=651...

[2] https://github.com/14427/signal

[3] https://github.com/adamhaile/S/tree/e897ec1212a073bb1fe695e1...


I feel like reactive programming is approaching ripeness for a mainstream programming language or cross-stack paradigm within the next few years. React, Svelte, Redux etc in the frontend world has certainly paved the way on the mainstream side, albeit in a simplified environment (singlethreaded, does not cross the network boundary and can simply share memory cheaply). I wonder if a refined version of this with will prevail, or something else like Rx? What are the primitive operations in such a model? Streams, futures, “watch” APIs?


ReactiveUI is based on Rx and is very popular in the .Net world: https://www.reactiveui.net/.


What's the difference between reactive and functional programming? Both seem to focus on expressing relationships between objects and having compiler/runtime infer the computation, instead of explicitly specifying the computation.


> Both seem to focus on expressing relationships between objects and having compiler/runtime infer the computation, instead of explicitly specifying the computation.

That's true of every declarative approach. Functional, reactive, relational, regex etc. are all to some degree declarative.

To answer your question: Functional programming is really more about avoiding mutation at the language surface, while reactive programming is about how mutation propagates through code, so it's an abstraction over specific mutation. This is why they pair nicely together.


Most of the time the term used is "Functional Reactive Programming". If that answers your question.

Although there exists a paper about "Reactive Imperative Programming" (with Dataflow Constraints): https://typeset.io/pdf/reactive-imperative-programming-with-...


Perhaps if you framed your question subtly differently and asked yourself what is the difference between Erlang and OTP, the answer maybe easier to discern?


What's the exact difference between Erlang and OTP that would answer my question?


OTP is the means by which the programming language is able to accomplish tasks such as process to process comms, how to supervise actors etc. It's the enabler for the distributed computing compared to Erlang which is the programming language. They're (obviously) complementary but OTP is the magic that enable one to deliver on the reactive manifesto. Functional programming needs augmenting with mechanisms that provide the distributed computing features, it's not enough on its own.


Thanks for the explanation.

Where does reactive programming fit in that concept? Is OTP a "reactive" library? If so, why - what exactly makes it "reactive"?


Elm was positioned to be this. Still technically could be, but adoption isn’t high. Original Elm had signals.


The native (future) solution for Apple platforms is SwiftUI [1] + Combine [2]. I use it on a side project and for somebody who works with React 9-5 it feels very natural. In short SwiftUI is like React & Combine is like RxJs.

[1] - https://developer.apple.com/tutorials/swiftui [2] - https://developer.apple.com/documentation/combine


While I agree, I’d say I find SwiftUI easier and more intuitive than React. And Combine often much more frustrating than Rx.


I'd go even one step further, diffusion/chemical/tissue-level programming. I'm sense the idea of multi agent state morphism becoming a norm in people's mind. (from css to to react to kubernetes).




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

Search: