However, now that I do understand it, I agree with parent. I don't want to use this for complex UIs. Point by point:
> Cancellation is then implicit on deallocation
In practice, you're going to have retain cycles, no? I mean I don't know where these four lines "live", but if we write them in a closure and ship them off to a sync/diff/runloop engine, unless we are quite careful, that sync engine is going to hold a great many strong references. Unless you intend this to be an IBAction definition, in which case...
> because it provides type safety and compiler verification, instead of hoping that your target/selectors and KVO work and never break when you edit something and the compiler doesn't complain.
As long as the underlying Cocoa uses target-action, true compiler verification is impossible. Compiler verification checks something (these four lines) but it doesn't check that these lines actually run when the slider is moved in any way.
> FRP-like systems allow you to take some evil imperative state (a slider was moved!), lift it into a happy pure-functional world,
It's not immediately clear to me how e.g. throttle is implemented, but it must accumulate state inside it somehow in order to replay the event after the timer.
> Complex UIs are exactly where you want this type of system, because it provides type safety and compiler verification, instead of hoping that your target/selectors and KVO work and never break when you edit something and the compiler doesn't complain.
Compiler verification is good; tests are better. And I do not understand how you would even begin to write unit tests for this.
Now we get to:
1. Stopping in the debugger and trying to reason about these signal chains is complicated, because what we have here is a datastructure in memory, not lines of code I can step through
2. This example does not account for threading, and in any nontrivial example you want to move between background and foreground a few times. It also does not deal with "splitting/merging" (multiple observers, etc.) and I suspect the intersection of those two features is a sharp edge.
3. Finally, let's compare against a slightly more traditional syntax:
@IBAction func valueChanged() {
dispatch_throttle(0.5, onQueue: dispatch_get_main_queue()) {
colorView.color = colorToValue(red: sliderA.value, green: sliderB.value, blue: sliderC.value)
}
}
This syntax is also four lines of code, including the context of where the lines live. This example resolves all the problems I listed with the FRP example. In addition, it also collates which slider goes with which color component in a single line, rather than breaking that relationship apart across a (potentially long) signal path.To evolve from your first example to your second example we would just change
slider.addTarget(self, action: "valueChanged", forControlEvents: .ValueChanged)
to button.addTarget(self, action: "valueChanged", forControlEvents: .TouchUpInside)
While I readily concede this aspect is not quite as elegant as your example, to me having a slightly more complicated 1-line diff is a very low price to pay for all the other benefits I listed.