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.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.
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.
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.
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.
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.
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.)
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".