Saturday, September 26, 2015

Very Simple Dataflow Constraints with Objective-Smalltalk

Early last year, I wrote a lengthy piece on the connection between Apple technologies such as Key Value Observing (KVO) and Bindings and general Computer Science concepts such as constraint solving, particularly dataflow constraints (aka. Spreadsheet Constraints).

I also wrote that I was working on something, and despite being somewhat distracted with becoming a father, joining a startup and being acquired, I now have working code.

The sample application contains two examples, one a classic temperature converter that I will cover later, the other an implementation of the ReactiveCocoa password validation example. The basic idea is super-simple, we want to enable a login button when the password field and the confirmation field contain the same value, expressed as follows in Objective-C:


loginButton.enabled = [password.stringValue isEqual:passwordConfirm.stringValue];

Again, this is super simple to write down, but it's not the entire story, because we want to evaluate this statement continuously as the password field and the passwordConfirm field change. The mess of callbacks require to keep those states in sync vastly exceeds the one-time evaluation, as explained in a very good article on Reactive Cocoa by NSHipster. That article uses a slightly more elaborate example, the one in the ReactiveCocoa documentation is the following:


RAC(self, createEnabled) = [RACSignal 
    combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ] 
    reduce:^(NSString *password, NSString *passwordConfirm) {
        return @([passwordConfirm isEqualToString:password]);
    }];

What's noticeable, apart from the macros that are necessary, is the semantic noise apparently inherent in this solution. Instead of focusing on what we want to accomplish (hidden inside the last return), the focus is on generic RAC classes such as RACSignal and methods such as combineLatest: and reduce:. I didn't really want to combine and reduce, I just wanted to keep some different states in sync, and with Objective-Smalltalk, I can do just that.

Let's first recast the original Objective-C expression into Objective-Smalltalk. Since Smalltalk is not burdened by the syntactic legacy of C, we can lose the square brackets. Because we have binary selectors (a bit like operators) and use ':=' for assignment, we can use '=' to check for equality instead of having to write out 'isEqual:'. The dots get replaced by slashes because Polymorphic Identifiers use URI syntax, and finally we use periods instead of semicolons at the end of sentences, er, statements.


loginButton/enabled := password/stringValue = passwordConfirm/stringValue.

Again, this is semantically the same statement as the original Objective-C, it assigns the right hand side to the left hand side. This can be viewed as a a one way constraint: the left hand side should be the same as the right hand side. The constraint has a fundamental flaw, though, because it is only maintained instantaneously as the line of code is executed. After that, left hand side and right hand side can diverge again. What we want is for that constraint to be maintained indefinitely: whenever the right hand side changes, the left hand side should be updated. In Objective-Smalltalk, you can now express this by replacing the ":=" assignment operator (technically: connector), with the "|=" constraint connector:
loginButton/enabled |= password/stringValue = passwordConfirm/stringValue.

A single character change, so no syntactic and no semantic noise.

Sunday, September 13, 2015

Why Software Is Hard

A while ago, the guys from the "Accidental Tech Podcast" had an episode about the goto fail; disaster and seemed to be struggling a bit with why software is hard / complex, "the most complex man made thing". Although the fact that it's all built is an important aspect, I think that that is a fairly small part.

Probably the biggest problem is the state-space. Software is highly non-linear and discontinuous, unlike for example a bridge, or most other physical objects. If you change or remove a single bolt from a bridge, it is still the same bridge and its characteristics are largely the same. You need to remove quite a number of bolts for that to change, and the effects become noticeable before that (though they do get catastrophically non-linear at some point!). If you change one bit in a piece of software, the behavior is completely unpredictable. It could be the same, it could just crash, it could quietly corrupt data. That's why all those corner cases in the layers matter so much. Again, coming back to the bridge, if one beam has steel that has a slightly odd edge-case, it doesn't matter so much, you don't have to know everything about every beam, as long as they are within rough tolerances. And there are tolerances, and you can improve your odds by making things with tighter tolerances than required. Again, with software it isn't really the case, discrete problems are much harder than continuous ones.

You can see this at work in optimization problems. As long as you have linear equations of real values, there are efficient algorithms for solving such an optimization problem (simplex typically runs in linear time, interior point methods are polynomial). Intuitively, restricting the variables to take only integer values should be easier/quicker, but the reverse is true, and in a big way: once you have integer programming or mixed-integer programming, everything becomes NP-hard.

In fact, I just saw this in action during Joe Spolsky's talk "You suck at Excel": he turned on goal-seeking (essentially a solver), and it diverged dramatically. The problem is that he was rounding the results. Once he turned rounding off, the solver converged to a solution fairly quickly.

The second part that they touched upon, is that it is all abstract, which I think is what they were getting at with the idea that is 100% built. Software being abstract means that we have no intuitions from physical objects to guide us. When building a house, everyone has an idea of how difficult it will be to build a single room vs. the whole house, how much material it will take etc. With software, not so much: this one seemingly little sub-function can potentially be more complex than the entire rest of the program. Even when navigating a hierarchical file-system, there is no indication of how much is hidden behind each directory entry at a particular level.

The last part is related to the second, in that there are no physical or geometric constraints to the architecture and connection complexity. Again, in a physical system we know that something in one corner has very limited ways of influencing something in a different corner, and whatever effect there is will be attenuated by distance in a very predictable way. Again, in software we cannot generally know this. Good software architecture tries to impose artificial constraints to make construction and understanding tractable.