What is reactive programming?(paulstovell.com)
paulstovell.com
What is reactive programming?
http://paulstovell.com/blog/reactive-programming
9 comments
The linked Stack Overflow answer by Conal Elliott does a much better job explaining FRP. It has less to do with "linking" as via this destiny operator and more to do with manipulating values of "entire histories and futures" all at once. In other words, the heart of FRP is not in the operators, but the nouns. And not in an OO sense tied to ambient mutation, but instead as concrete, immutable values.
To save others clicking through the article again, the SO question is here: http://stackoverflow.com/questions/1028250/what-is-functiona...
I knew it was by Conal before I got to the end, as soon as I saw the phrase "denotational semantics" ;)
I should really learn what that means one day...
I should really learn what that means one day...
Denotational semantics: code is a meaningful artefact.
This fits well with the functional paradigm, where values are immutable and therefore 'timeless'.
In contrast, imperative code, if it has any actual semantics at all, tends to be operational:
Operational semantics: the "meaning" of code is how it affects the behaviour of the machine that's interpreting it.
The problem with operational semantics is exactly that of imperative code: in order to understand what some code will do, you need to know everything which 'happened before' (ie. the current state of the machine).
This fits well with the functional paradigm, where values are immutable and therefore 'timeless'.
In contrast, imperative code, if it has any actual semantics at all, tends to be operational:
Operational semantics: the "meaning" of code is how it affects the behaviour of the machine that's interpreting it.
The problem with operational semantics is exactly that of imperative code: in order to understand what some code will do, you need to know everything which 'happened before' (ie. the current state of the machine).
>> The problem with operational semantics is exactly that of imperative code: in order to understand what some code will do, you need to know everything which 'happened before' (ie. the current state of the machine).
Wouldn't knowledge of everything within the current scope be sufficient?
Wouldn't knowledge of everything within the current scope be sufficient?
No, because every function you call could be depending on hidden state that's not in your scope.
What, then, is the problem with denotational semantics?
In order to understand how it's actually going to play out on a real machine in real time, you have to be able to simulate the execution (and perhaps the compiler), either experimentally or in your head.
I have to confess I'm basically trolling here. I mean, I hope to get an answer, but my expectations are low. I feel like I'm asking a salesman for why I shouldn't buy their product.
It isn't even that I don't think they have a good product. They certainly do. One need only actually read SICP to realize just how bloody amazing some of these ideas are. (Granted, they didn't use all of the modern names. But they certainly seem to have hit all of the modern ideas.)
It isn't even that I don't think they have a good product. They certainly do. One need only actually read SICP to realize just how bloody amazing some of these ideas are. (Granted, they didn't use all of the modern names. But they certainly seem to have hit all of the modern ideas.)
Isn't that equally true (if not more so) for imperative/operational semantics?
It's somewhat true for imperative - you still have to keep an eye on your call graph, et cetera.
But in Haskell, say, because of lazy evaluation, it's easier to lose track of how much CPU effort something is going to cost, and when you're going to have to pay for it.
But in Haskell, say, because of lazy evaluation, it's easier to lose track of how much CPU effort something is going to cost, and when you're going to have to pay for it.
It is more than that. In many of the popular imperative languages, any memory modifications are done by the programmer. Not just memory, but sometimes processing capabilities. When things get hidden behind an abstraction over "map", say, suddenly what you thought was a single function call was actually a crapload more.
This is becoming less true, of course. Java getting lambdas hides a TON of places where you just managed to allocate a ton of memory and/or perform a ton of operations.
And note, I do think this downside can be oversold. So, don't take this as a condemnation of "functional" languages and methods. It definitely exists, though.
Granted, I am almost certainly simply infatuated with what is essentially an "anti immutable" algorithm. http://taeric.github.io/Sudoku.html
This is becoming less true, of course. Java getting lambdas hides a TON of places where you just managed to allocate a ton of memory and/or perform a ton of operations.
And note, I do think this downside can be oversold. So, don't take this as a condemnation of "functional" languages and methods. It definitely exists, though.
Granted, I am almost certainly simply infatuated with what is essentially an "anti immutable" algorithm. http://taeric.github.io/Sudoku.html
This gives me an great idea. What if we put these variables into a grid displayed on the screen? Then you will be able to enter some values into some of the cells, and the dependent ones update themselves automatically. The elements of the grid could be regarded as array elements indexed by coordinate, and use relative or absolute addressing to refer to each other's coordinates, making it easy to replicate dependency rules across rows and columns, to generate entire tables which update themselves.
What a novel idea!
Seriously though, Angular gives you the ability to do this in the browser, here's an example of how to implement an Excel-like spreadsheet using Angular: http://thomasstreet.com/blog/legacy/spreadsheet.html
Seriously though, Angular gives you the ability to do this in the browser, here's an example of how to implement an Excel-like spreadsheet using Angular: http://thomasstreet.com/blog/legacy/spreadsheet.html
It's not really unique to Angular is it?
No, I think the other major client-side MVC frameworks (Backbone, Ember, React) are also geared toward this reactive paradigm.
Kenny Tilton's "Cells" project (Common Lisp) implements CLOS slots that are linked to other slots and automatically update. I believe this precedes the reactive programming fad by many years.
The spreadsheet, the one "graphical programming language" to make it into widespread use by nonexperts.
And in practice "use relative or absolute addressing" is resolved via random number generation. Any later problems resulting from confusing the two can just be fixed by filing tickets with IT.
Also used as the corporate standard database management system and as what boils down to a mail merge client, and although this appears to outsiders as bad standup comedy, it is unfortunately merely factual.
Possibly one of the quickest ways to measure the dilbertian level of rot of an organization is to propose a small database and ask what tool they'd use to hold that information. Mysql, OK. Postgresql, eh, OK. Oracle, ummm. Excel, find the exit door and start running. Microsoft Word, Cthulhu take us to end the agony. I've seen all of the above. And survived. Barely. Perhaps not with my sanity intact.
edited: spreadsheets fit into the design pattern of jokes where you got a problem to fix, so you try to implement a spreadsheet to fix it, now you got two problems to fix. This is probably funnier in its original regex and perl and web framework variants.
Also used as the corporate standard database management system and as what boils down to a mail merge client, and although this appears to outsiders as bad standup comedy, it is unfortunately merely factual.
Possibly one of the quickest ways to measure the dilbertian level of rot of an organization is to propose a small database and ask what tool they'd use to hold that information. Mysql, OK. Postgresql, eh, OK. Oracle, ummm. Excel, find the exit door and start running. Microsoft Word, Cthulhu take us to end the agony. I've seen all of the above. And survived. Barely. Perhaps not with my sanity intact.
edited: spreadsheets fit into the design pattern of jokes where you got a problem to fix, so you try to implement a spreadsheet to fix it, now you got two problems to fix. This is probably funnier in its original regex and perl and web framework variants.
That's what Lotus Improv did, twenty years ago. It was too radical for users at the time and so sales were pretty much nonexistent.
https://en.wikipedia.org/wiki/Lotus_Improv
https://en.wikipedia.org/wiki/Lotus_Improv
That's also what VisiCalc did; released in 1981 on Apple II machines:
https://en.wikipedia.org/wiki/VisiCalc
https://en.wikipedia.org/wiki/VisiCalc
Scaling would be quite difficult. I'm sure you could hack maybe a Turing machine out of this paradigm, but it would be ugly and mostly an esoteric exercise.
Netflix uses reactive programming massively
https://www.youtube.com/watch?v=XRYN2xt11Ek
Interesting talk. Thanks for sharing!
I've been slowly implementing my own functional reactive programming system over for a game engine I'm writing. A few months ago, I wrote a short blog post on the topic that includes a screencast to see how it all works. Maybe it will help someone understand (F)RP a bit better.
http://dthompson.us/functional-reactive-programming-in-schem...
http://dthompson.us/functional-reactive-programming-in-schem...
Thanks. I've been reading about FRP, and I think games or GUI centric applications are a great use-case for it.
The "destiny" operator is just function definition. It might seem different if you're only used to languages where function calls and variable accesses are syntactically distinct.
Not true. Consider the following snippet -
The "destiny" operator is more like
a = 10
b = function() { return expensive_computation(a) }
Now, even if you can call b without parens, it still has to redo the expensive calculation every time you access it (because a might have changed... even if it hasn't).The "destiny" operator is more like
a = 10
b = function() {
persistent cached_value = null
if is_null(cached_value) or has_changed(a) {
cached_value = expensive_computation(a)
}
return cached_value
}
but instead of that, you get to write (in this totally fictional programming language) a = 10
b <- expensive_computation(a)
and have the compiler take care of the caching and updating for you.> Now, even if you can call b without parens, it still has to redo the expensive calculation every time you access it (because a might have changed... even if it hasn't).
That's true in most programming languages, but not in languages that distinguish side-effect free code. (Such a language implementation might still rerun a pure function because it doesn't include a way of "remembering" that a function has been called with particular arguments before, but that's an implementation detail.)
That's true in most programming languages, but not in languages that distinguish side-effect free code. (Such a language implementation might still rerun a pure function because it doesn't include a way of "remembering" that a function has been called with particular arguments before, but that's an implementation detail.)
Plus the real power of reactivity is that it is push, not pull. The changes are propagated without ever needing to poll for the new value manually - for instance, you could display the value of b in a UI and have it change whenever the value of a changes.
Nothing I said implies either push or pull. Sufficiently smart runtimes can do both based directly on function definitions.
Even Excel knows how to bind a displayed value directly to functional dependencies and only recalculate when necessary.
I'm not denying these techniques are useful, I'm just pointing out that a lot of what seems nice about them is actually attempts to reimplement functional programming inside languages that don't do it very well natively.
Even Excel knows how to bind a displayed value directly to functional dependencies and only recalculate when necessary.
I'm not denying these techniques are useful, I'm just pointing out that a lot of what seems nice about them is actually attempts to reimplement functional programming inside languages that don't do it very well natively.
Isn't the push strategy an expensive one? Maybe not all of the dependencies are needed right away, why bother with dispatching a new value though all of them? ...and more, considering that distinct dependency updates may require different (and admittedly expensive) computations, won't the push way be an overkill?
I'm not sure pull is any better in this regard. Simply stated, naive push and pull are both bad. You wind up putting a fair bit of heuristics on top of either to make them good.
Pull has the major downside that you never know when you need to pull a new value. Which is a pretty major downside.
And really, this does just feel like we baked some new semantics onto the observer pattern.
Pull has the major downside that you never know when you need to pull a new value. Which is a pretty major downside.
And really, this does just feel like we baked some new semantics onto the observer pattern.
It depends on your point of view. From one perspective, the observer pattern is simply a way to implement reactive values in the OO paradigm - indeed, a somewhat arrogant FP aficionado would describe many of the GoF design patterns as clumsy OO emulations of things a functional language would be able to express much more elegantly.
In the Rx-style reactive model the big thing is making the event streams, in addition to the events, first-class objects, so they can be composed and manipulated with the usual FP subjects like map/filter/reduce. This makes, for instance, throttling to optimize the propagation of changes almost trivial compared to the situation where you only have individual events without context.
In the Rx-style reactive model the big thing is making the event streams, in addition to the events, first-class objects, so they can be composed and manipulated with the usual FP subjects like map/filter/reduce. This makes, for instance, throttling to optimize the propagation of changes almost trivial compared to the situation where you only have individual events without context.
Yeah, I've read some of the papers. I was even heavily swayed for a time. However, I'm not entirely sure that Streams are an automatic win in abstraction. Often combining streams is just a very clumsy way of saying "add this and that."
More specifically, I think things are a lot easier when you can work way above those abstractions. As soon as you get into the weeds, all of the abstractions suck.
I also think this is specifically why people like angular so much. For many use cases, you are just setting values and having the UI present said values. Note, setting values, not appending to streams.
Now, do I expect that angular's internals would benefit from the streams metaphor? I certainly suspect so. Don't know, though.
More specifically, I think things are a lot easier when you can work way above those abstractions. As soon as you get into the weeds, all of the abstractions suck.
I also think this is specifically why people like angular so much. For many use cases, you are just setting values and having the UI present said values. Note, setting values, not appending to streams.
Now, do I expect that angular's internals would benefit from the streams metaphor? I certainly suspect so. Don't know, though.
There are also push-pull semantics which try to the the best of both worlds.
You're reinforcing my point. You're assuming imperative semantics.
Wherever "destiny" would be appropriate, you necessarily have a referentially transparent function. Wherever you have a referentially transparent function, a smart compiler or runtime can avoid recalculating anyway, without bothering the programmer with the distinction between "function" and "destiny".
Wherever "destiny" would be appropriate, you necessarily have a referentially transparent function. Wherever you have a referentially transparent function, a smart compiler or runtime can avoid recalculating anyway, without bothering the programmer with the distinction between "function" and "destiny".
That's called memoization and you usually want to leave this decision up to the caller and not the callee since only the caller knows if they are fine with receiving the same value as calculated previously or whether they want a new one each time.
Only relevant if the function isn't pure.
A proper function will always return the same value if you give it the same input, so there's no need for the computer to always re-run the expensive computation.
Right. Without state using only pure functions, you get to ignore change and time, therefore even the notion of "reactive programming" is not very meaningful.
That functions are pure means that their inputs and outputs are well defined, not that they ignore or don't work with things like state or time.
Before monads were 'invented' for functional programming, reactive programming (or stream based I/O) was the only way of handling state and side effects in Haskell.
Before monads were 'invented' for functional programming, reactive programming (or stream based I/O) was the only way of handling state and side effects in Haskell.
I enjoyed the post, but I think the proposed scope of reactive programming is a bit limited. What the author describes is, I think, more related to the concerns of "bidirectional programming"[1] and lenses[2]. Granted, I think those concerns do fit broadly under the umbrella of reactive programming.
Below is Prof. David Harel's (the inventor of statecharts, formalized as UML State Machines) general characterization[3] of a reactive system; reactive programming would then be any programming methodology, language or environment which aims to service those characteristics.
* It continuously interacts with its environment, using inputs and outputs that are either continuous in time or discrete. The inputs and outputs are often asynchronous, meaning that they may arrive or change values unpredictably at any point in time.
* It must be able to respond to interrupts, that is, high-priority events, even when it is busy doing something else.
* Its operation and reaction to inputs often reflects stringent time requirements.
* It has many possible operational scenarios, depending on the current mode of operation and the current values of its data as well as its past behavior.
* It is very often based on interacting processes that operate in parallel.
[1] http://www.cs.cornell.edu/~jnfoster/papers/jnfoster-disserta...
[2] http://www.cis.upenn.edu/~bcpierce/papers/lenses-etapsslides...
[3] http://www.wisdom.weizmann.ac.il/~harel/reactive_systems.htm...
Below is Prof. David Harel's (the inventor of statecharts, formalized as UML State Machines) general characterization[3] of a reactive system; reactive programming would then be any programming methodology, language or environment which aims to service those characteristics.
* It continuously interacts with its environment, using inputs and outputs that are either continuous in time or discrete. The inputs and outputs are often asynchronous, meaning that they may arrive or change values unpredictably at any point in time.
* It must be able to respond to interrupts, that is, high-priority events, even when it is busy doing something else.
* Its operation and reaction to inputs often reflects stringent time requirements.
* It has many possible operational scenarios, depending on the current mode of operation and the current values of its data as well as its past behavior.
* It is very often based on interacting processes that operate in parallel.
[1] http://www.cs.cornell.edu/~jnfoster/papers/jnfoster-disserta...
[2] http://www.cis.upenn.edu/~bcpierce/papers/lenses-etapsslides...
[3] http://www.wisdom.weizmann.ac.il/~harel/reactive_systems.htm...
https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 is an excellent introduction to reactive programming.
Wait, we're still using semicolon line terminators in 2051?
Sigh.
Sigh.
Pretty sure that's a mistake in the article. We're supposed to be in 2051 looking back at a sample of code written in 2021.
...and mutable values by default.
... and single character ASCII non-UTF-8 variable names.
... and no types. So if a and b are strings in a "mostly string-ish language" then the value of b is "101" halfway thru and "111" at the end, right?
(I'd cut them a break if they used a single expressive and meaningful kanji even ifs only one glyph, although I'm struggling to think of a kanji that implies type and a compiler smart enough to understand the type embedded in the kanji... although it could happen)
... and no types. So if a and b are strings in a "mostly string-ish language" then the value of b is "101" halfway thru and "111" at the end, right?
(I'd cut them a break if they used a single expressive and meaningful kanji even ifs only one glyph, although I'm struggling to think of a kanji that implies type and a compiler smart enough to understand the type embedded in the kanji... although it could happen)
Reactive = Responding to (often asynchronous) events
The words "responding" and "asynchronous" seem to imply a particular execution model, in particular one which seems to contradict the original definition of Function Reactive Programming
http://conal.net/papers/icfp97/
Perhaps a more neutral description would be:
Reactive = Values are parameterised by the current time
http://conal.net/papers/icfp97/
Perhaps a more neutral description would be:
Reactive = Values are parameterised by the current time