The Appeal of Effortless Reactivity
I appreciate [bindings] and [Key Value Observing]. They excel at achieving a certain coolness: after some initial setup, they work their magic. Change a value here, and another value there updates automatically. It’s like action at a distance, pure power.
Their value is undeniable. Nobody enjoys writing tedious state management code: capturing button clicks, synchronizing text fields, and notifying listeners about changes. It’s a breeding ground for subtle bugs.
Their implementation is impressive too: observing an object’s attribute secretly creates a subclass, substituting the original methods with hooks into the KVO system. Prototype-based programming in Objective-C, who knew?
Yet, despite these advantages, I’ve stripped bindings from my projects. I steer clear of KVO and advise others to do the same. Why?
A Detour into Constraint Solvers
Before answering, let’s rewind and discuss [constraint solvers].
The concept of defining relationships once and letting the system maintain them isn’t new. An early example is [Sketchpad], Ivan Sutherland’s groundbreaking [PhD Thesis] (1961/63). Video of Alan Kay narrating Sketchpad. Ivan’s response to how he achieved such feats simultaneously: “I didn’t know it was hard.”
[ThingLab], built on Smalltalk at Xerox PARC around 1978, was among the first to merge constraint solving and object-oriented programming.
Explore the ThingLab papers, particularly [this one on Smalltalk](The Programming Language Aspects of ThingLab, a Constraint-Oriented Simulation Laboratory) (PDF link). Notably, ThingLab introduced Paths, symbolic references to object parts.
While seemingly simple, paths are powerful, bridging constraint and object-oriented paradigms. [..] They reinforce object boundaries, providing controlled external access to internal components.
Essentially, a superior KVC, from 1981! Alan Borning’s research at the University of Washington continued this legacy, culminating in [Cassowary], adopted by Apple for Autolayout. Understanding Cassowary and constraint hierarchies illuminates Autolayout’s behavior.
One-way dataflow constraints are a simpler form:
They follow the equation y = f(x1,…,xn), where y automatically updates when any xi changes. External y modifications lead to temporary unsatisfaction, hence “one-way.” Their versatility and simplicity find applications in spreadsheets and beyond.
CMU researchers extensively used [these] [systems], publishing invaluable experience reports: [short version](Lessons Learned About One-Way, Dataflow Constraints in the Garnet and Amulet Graphical Toolkits (pdf)), [detailed version](Postscript version).
Key takeaways:
- Constraints should accommodate arbitrary code from the toolkit language without annotations.
- Debugging constraints is challenging and demands better tools.
- Programmers embrace constraints for layout but require significant training for other uses.
These are just highlights. The reports offer deeper insights, particularly for Cocoa developers.
Back to KVO and Cocoa Bindings
How is this constraint programming history relevant? Bindings are essentially one-way dataflow constraints, with the equation limited to y = x1 (NSValueTransformers enable complexity). KVO acts as an [implicit invocation] mechanism, often for ad-hoc dataflow constraints.
Issues with the API and implementation are well-documented, for instance, by [Soroush Khanlou] and Mike Ash, who proposed and implemented enhancements in [2008] and later revisited them in [2012]. These problems and [workarounds] highlight the complexity and fragility of KVO and Bindings for a seemingly simple task: data synchronization.
Performance is another concern. Merely adding willChangeValueForKey: and didChangeValueForKey: to a setter (often auto-generated) incurs a 30x slowdown even without notifications. Triggering a notification worsens it to over 100x, even with one observer. While 500ns might seem trivial for UI, KVO’s use in model layers can lead to significant cumulative overhead.
Moreover, the constraint graph is implicit, making it difficult to grasp without mentally mapping code and IB settings. Formulae are absent, replaced by ValueTransformers and keyPathsForValuesAffectingValueForKey.
This situation likely stems from a lack of awareness about existing constraint solver research.
Therefore, while a robust constraint system would be fantastic, KVO and Bindings fall short. Their simplicity and fragility outweigh the benefits. Writing explicit state maintenance code, though tedious, is ultimately more manageable.
A communication gap exists between proponents, who champion the concept, and critics, who target the implementation. Bridging this gap is crucial.
Functional Reactive Programming
Functional Reactive Programming (FRP), specifically [Reactive Cocoa], seems to address the same need.
[..] blends declarative [..] and imperative object-oriented programming, using constraints to explicitly represent relationships previously implicit in code.
FRP, right? This description resembles [[..]" part is actually “Constraint Imperative Programming” and the second is “constraints”, from the abstract of a 1994 paper]. Some even consider FRP as [like a spreadsheet]. The link between functional and constraint programming is well-established, as noted in the CMU report:
Given their shared foundation, it’s unsurprising that 1) programmers rarely use constraints beyond layout and 2) extensive training is needed to overcome procedural thinking.
However, FRP literature mainly focuses on its connection to functional programming through [functional reactive animations] and Rx extensions, neglecting the constraint aspect. This framing is problematic: while inherently static functional programming needs reactivity, it’s intrinsic to OO, where objects react to messages.
Adding reactivity to OO might seem [non-sensical], even [causes] [confusion] when explained this way. I was puzzled until discovering this work on reactive imperative programming, which elegantly incorporates constraints into C++.
Architecture
One-way dataflow constraints between variables can often be replaced by methods that compute dependent values on demand. This eliminates the need for constraints and dedicated classes but introduces permanent coupling. Constraints then become an architectural tool, enabling the composition of stateful components without tailored adaptations.
Everything Flows
“Panta rhei” - everything flows. Interestingly, FRP and OO communities converged on dataflow-based solutions. FP sought reactivity by modeling time as value streams, akin to [[Lucid](http://en.wikipedia.org/wiki/Lucid_(programming_language)]. OO aimed for declarative relationship specification, leveraging dataflow constraints similar to FRP.
This state maintenance and propagation pattern appears in various domains: makefiles, build systems, web servers, and more. Think automatic updates in documents with embedded external content.
Ironically, these groups seem unaware of each other. Communication plays a role: FP’s “state is the enemy” rhetoric doesn’t resonate with OO practitioners, for whom state is often fundamental. Moreover, practical experience shows that FP’s approach isn’t inherently obvious:
Training students to use constraints beyond layout is time-consuming, suggesting limited applicability. This isn’t surprising. While constraints align with mental models in spreadsheets and graphical layout, everyday tasks are often approached imperatively.
Unix pipes and filters inhabit a similar space, unsurprisingly given their dataflow nature:
Shell scripting readily composed processes. However, relying on intermediate files obscured the program’s compositional clarity, unlike the piping symbol, highlighting the significance of notation.
This familiarity fuels the desire for such mechanisms in general-purpose languages, especially after witnessing the conciseness achieved in systems like [Gezira], written in under [400 lines of code] using the [Nile] dataflow language.
The Path Forward
The allure of a more spreadsheet-like programming model is undeniable. It could simplify development and empower end-user adaptation, fostering a richer form of [open source]. How do we get there?
Beyond a robust implementation and improved [debugging] [support], tighter language integration is crucial. Direct constraint syntax, like in constraint imperative programming [languages] or language extensions like [Ruby] or [JavaScript], would be ideal. This support should be unified across constraint systems.
My [Objective-Smalltalk] project aims to address this, as evidenced by its [Polymorphic] [Identifiers], which offer a consistent interface for data from various sources, including constraint stores. Further steps involve extending the data-flow connector hierarchy to integrate constraints conceptually.
The goal is to provide a language that doesn’t mandate constraints but allows their seamless integration when needed. While a work in progress, the results are promising, especially with Objective-Smalltalk’s architectural focus aligning well with the architectural view of constraints.
Challenges remain, but the potential payoff is substantial. Leveraging existing research can guide the way. Your feedback, contributions, and [pull requests] are highly valued!
Join the discussion on [Hacker News].
Update: Progress has been made, with code and a brief [article] available.