| So, this is the first time I actually understand what the FRP people are talking about. So, thanks for that. It has been greek to me for some time. 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. |
> 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.
Certainly, a lot of FRP (and functional programming in general!) concepts involve state. I would never argue that there is no state in functional programming, just that the state is more explicit, localized, and easier to reason about.
In fact, in pure FRP, there are two fundamental building blocks: Behavior and Event. A Behavior is simply a value over time, and an Event is a value at an instant in time. Therefore, Behaviors are where you store state. However, what makes them "easier to reason about" is that oftentimes the API forces you to construct them in a way that makes all of the possible ways to change the Behavior are clear and present at once.
In summary, all inherently stateful concepts require stateful code, at lease somewhere under the hood. I would say that FRP and functional programming just make the state explicit.