Reactive Programming is the New OOP
dataflowbook.com
dataflowbook.com
There is a very long thread on the Go list of a guy who is a fan of dataflow grumbling about how design decisions made in Go make it a poor fit for dataflow.
Mostly agree, in the sens that, from my limited pratical experience & knowledge of Reactive Programming (RP), I understand that RP isn't bound to a particular "parallel execution model" (maybe s/parallel execution model/threading model/ ?).
> There is a very long thread on the Go list of a guy who is a fan of dataflow grumbling about how design decisions made in Go make it a poor fit for dataflow.
I can't find it in https://groups.google.com/forum/#!searchin/Golang-nuts/React...
A blog post might be better fit for that (so far I was unsuccessful getting constructive feedback that way). Anyway here are more thought, based on my experience dealing with GUI app:
- "parallelism", even if simpler with an event-loop, is still a significant burden especially when performance matters
- Class based OOP, especially not JavaScript, is very helpful to group a sequence of callbacks to help fight spaghetti code.
- Explicit State Machines are a praised black art wrongfully dubbed useless overhead because "it's possible to do otherwise"
- «yield/await/async» as been recently recognized broadly as the way forward cf. https://news.ycombinator.com/item?id=4732924
All the points mentionned above have in common that they try to handle correctly with fine grained priority and different "paralellism" patterns with ease.
RP mean to provide much more than "yield", it's pattern that is similar to the explicit state machine intent. The declarative paradigm/syntax arguably provides an ease of use that is competitive both in terms of performance and dev efforts even if mileage may vary. See by yourself:
>>> HackerNewsPost.rank = HackerNews.upvote_count * MAGIC_NUMBER
This one might be better expressing the actual behavior, even if the ranking algorithm is "hidden":
>>> HackerNewsPost.on_change(HackerNewsPost.position).update(HackerNewsPost.rank).maximum_frequency(timedelta(seconds=60*60))
The above can not be expressed in "one line" using "yield" and whatnot.
May be of interest to you:
- https://speakerdeck.com/ryanartecona/a-unified-model-of-asyn...
- http://swannodette.github.io/2013/12/17/the-future-of-javasc...
- Observable/Observer, pub/sub & signaling patterns
- Search for "AngularJS performance issues"
- http://en.wikipedia.org/wiki/UI_data_binding
Reactive Programing can abstract the underlying "threading model" and all the above patterns... I am biased.
edit: typo & more rigorous "API" in second one-liner
It’s funny how similar the arguments against OOP and
Reactive Programming are.
It's funny how similar this argument in favor of Reactive programming is to the arguments used in favor of other purported paradigm shifting trends that never went anywhere. Aspect Oriented Programming anyone?The most common complaint I've heard about reactive programming is that it has yet to be realized in the form of mature libraries. (In general, for most languages. I'm sure at least one language has it.) Thus, whether it's a great idea or not is largely immaterial for most workaday developers, who won't be able to use it until the ecosystem exists.
- Actors have no control over where messages come from. Actors have complete control over what other actors receive their messages.
- In a reactive model, a producer sends messages blindly, with no control over the receiver. The consumer chooses its sources specifically.
I'm not sure if this is exactly the right answer, but it is the impression I got after reading a paper on dataflow programming [1]. I'd be interested in whether anyone has any deep insights on the relative advantages of each model, and whether they're actually equivalent on some level.
[1] http://infoscience.epfl.ch/record/176887/files/DeprecatingOb...
Somewhere, Hoare gives an example of pipelining the sieve of Eratosthenes through a series of communicating processes, which is a good example of dataflow programming -- you send a list of integers to a process that sieves out even numbers and sends the result to a process that sieves out multiples of 3, and so on. Such a pipeline is deterministic -- given a set of values, you will always get the same output -- and compositional -- the sum of the sieves is greater than the individual parts.
A traditional actor-based model, on the other hand, isn't well-suited for that kind of problem, but is great if your goal is to dispense biscuits and chocolates. State is kept locally, and the actor can change its behavior to external messages based on internal state -- that is to say, actors have discrete identities. The response to any particular message is nondeterministic, because it depends on what's the actor's internal state and decision process may be, and you can't really combine multiple vending machines to compose a new, super-vendor.
The Actor model is basically OO, except that objects are given their own execution contexts, whereas in OO there's one execution context that drives the objects. You can do worse for your first design in Erlang than to break your task into core objects exactly as you would for a high-level OO design (don't put in all the little collection and iterator and other structure classes, stick to the "high level" design), and use that to drive your process design. It won't be perfect, but it's not a bad first pass. The profound differences between one execution context and multiple also has subsequent follow-on effects on the languages in question, and in what direction they evolve, so in practice they feel quite different despite the underlying similarities in the model.
Reactive programming is extremely different. There, values are defined as functions on other values, intrinsically, such that if A is B + C, future changes to B and C mean that A is automatically updated to the new B + C with no programmer interaction. This is no longer imperative design; instead of your program consisting of a huge set of instructions for the processor to directly execute (no matter how gussied up that is by the syntax and semantics of the local language), your program consists of a declaration of relationships between values and how they evolve over time. The fundamental operation of the programming language is no longer "Set value identified by X to Y", but "Attach value X to value Y with update function Z".
As is always the case in the world of Turing Completeness, this can be embedded into OO, or OO can be embedded into it, etc. all sorts of combinations are possible. But it is a fundamentally different underlying method for writing your program.
I would observe that A: this isn't new, which I say not because anyone is claiming that it is but because it means that we can in fact draw on past experience to examine it and B: our general experience with it up to this point is that it often makes small tasks easier, even much easier, but that trying to create whole systems this way is often quite problematic. If you think laziness in Haskell is hard to understand, just wait until you're trying to understand a cyclic dependency graph in a data-binding environment (the usual OO manifestation) or a spreadsheet, with the local bizarre adaptations to try to make that sensible all interacting with each other in crazy complicated ways. It's a useful tool, and I don't mean that as a mere rhetorical sop, it really is a useful tool to have in the belt, but I'm deeply skeptical of it as a proclaimed savior of programming, or the Next Big Paradigm. Our experiences with it up to this point have not been positive enough to justify that claim, IMHO, and I haven't seen that there's been any sort of big enough change in the landscape lately to justify that.
Compare to "functional programming", which has been the Next Big Paradigm for a few decades now, but IMHO modern Haskell of the past 3-5 years really is a big enough advance to justify some examination on the topic. (I'm not saying Haskell is guaranteed to be the Next Big Thing, I'm just saying that there's been enough action there to legitimately justify having a fresh look at the situation.)
If you can point me at the big advance for reactive programming in question, I'd love to have a look. I know the Haskell world has been fiddling with FRP but I'm still underwhelmed by the efforts.
I think that may be a significant, fundamental problem with reactive programming. The way these graphs are constructed is typically (necessarily?) buried within the reactive programming framework, giving the programmer poor visibility into what's actually happening. Maybe that problem can be solved, but I suspect it might be intrinsic to reactive programming.
> I know the Haskell world has been fiddling with FRP but I'm still underwhelmed by the efforts.
I've test-driven some of those projects. Last I checked, the documentation was extremely sparse, and the only examples were trivial. I haven't yet seen an example of a useful Haskell app built with FRP. FRP has sometimes been described as the silver bullet for doing stateful apps in a pure FP way. Maybe it is, but for now, I'm sticking with pseudo-imperative Haskell for those purposes.
Not really. There are OOP languages, there are multi-paradigm languages that include OOP stuff, and then there are languages that don't have OOP at all. That last category includes functional languages like Haskell and some Lisps.
Should every language support at least some OOP? Maybe. I think it's too soon to tell. We may eventually learn that pure functional languages are better for maintaining a sane codebase, in which case OOP will not be considered a mandatory language feature.
Multi-paradigm languages: I'll admit they're usually woefully lacking in sex appeal. When it comes to getting @$#@% done and moving on with your life, though, they be awesome.
Interesting. Can you point me to an example?
http://okmij.org/ftp/Scheme/oop-in-fp.txt
http://stackoverflow.com/questions/2954642/methods-and-prope...
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-20.html
Some examples. The first is a discussion on it. The second and third have examples in scheme. I'd type more but I'm on my iPad. If I get time later I'll steal time on someone's computer to type up more.
data Surface = Surface -- presumably provided by drawing backend
data Shape = Shape
{ shapeGetCenter :: (Int, Int)
, shapeSetCenter :: Int -> Int -> Shape
, shapeDraw :: Surface -> IO ()
, shapeScale :: Double -> Shape
, shapeGetAABB :: (Int, Int, Int, Int)
, shapeShow :: String
}
instance Show Shape where
show = shapeShow
circle r x y = Shape
{ shapeGetCenter = (x, y)
, shapeSetCenter = circle r
, shapeDraw = undefined -- draw shape to surface
, shapeScale = \ f -> circle (f * r) x y
, shapeGetAABB =
( floor $ fromIntegral x - r/2
, floor $ fromIntegral y - r/2
, ceiling $ fromIntegral x + r/2
, ceiling $ fromIntegral y + r/2
)
, shapeShow = unwords [ "circle", show r, show x, show y ]
}
box w h theta x y = Shape
{ shapeGetCenter = (x, y)
, shapeSetCenter = box w h theta
, shapeDraw = undefined -- draw shape to surface
, shapeScale = \ f -> box (w * f) (h * f) theta x y
, shapeGetAABB =
let w' = h * abs (sin theta)
+ w * abs (cos theta)
h' = w * abs (sin theta)
+ h * abs (cos theta)
in ( floor $ fromIntegral x - w'/2
, floor $ fromIntegral y - h'/2
, ceiling $ fromIntegral x + w'/2
, ceiling $ fromIntegral y + h'/2
)
, shapeShow = unwords [ "box", show w, show theta, show x, show y ]
}