The Swift Safety Distraction

I keep thinking about why I feel uneasy about the current fad of static typing and functional programming.

I mean, I don't have anything against either. Well, maybe a little bit against static typing but only because it can be a real nuisance sometimes. But I've been interested functional programming ever since I first ran into Erlang nearly 10 years ago. I've been trying to carry along ideas I've picked up from other languages and frameworks into every new language, platform, and framework I pick up.

I do like Swift; I think it's one of the most interesting and practical languages I use today. I sometimes feel like people want me to despise Objective-C, now, which I can't do. I consider it one of the most interesting languages with some of the best frameworks on some of the coolest platforms I've ever had the joy to work with.

I think what's bugging me is the focus on "safety"; this idea that Swift is great because it will prevent us from writing "bad code" and force us to write good code. I can't shake the feeling this is a useless and maybe even dangerous distraction. I'm not certain, I could easily be wrong here, but I want to get this funny feeling off my chest, to talk it out.

I feel Swift is an improvement because it gives us more power with less effort. If the compiler can catch a couple extra errors for me, that's a bonus, but that alone doesn't seem to be all that special.

To illustrate, let's take generics. They most often seem to be presented as tools to prevent mistakes. Well, I feel generics are best when they are used like a lever, not a gate. A good generic type design can make code more expressive and more dense – less cruft. But that takes mostly design effort, not necessarily a language feature.

Take generic collections; we're mostly told they're great because they prevent us from adding the wrong type to a collection.

var strings:[String] = []
strings.append(42) // no, no, no! not it my house!

Um, OK, I guess. But, I don't really need you to protect me from myself. In 25 years of professional programming, I don't think I've ever had a problem adding the wrong type to a collection. Not once.

Now, I've gotten collections that had mixed types in them, like parsed JSON, and I've screwed up handling them (or inherited code that did), but generics don't help me here. Why? Because the core problem remains. If the code I'm calling is giving me collections with mixed types, making it use generics isn't going to fix a bad design. In fact, the code will probably just declare the return type as [Any] and call it a day (like JSON). Now I'm stuck worrying about the same problem and the static nature of the language is just going to require more ceremony. I'm probably going to end up with lots of checking and casting code and I might still get errors at runtime.

Granted, Swift's optionals can really help here, but the advantage has nothing to do with generics or type safety. It's the introduction of an optional type, a change in the semantics of nil. Take this contrived JSON example in Objective-C:

NSString *json = @"{\"name\": \"sam\", \"height\": { \"feet\": 5, \"inches\": 8 } }";
NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *person = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSDictionary *height = person[@"height"];
if (height) {
    NSNumber *feet = height[@"feet"];
    NSNumber *inches = height[@"inch"]; // intentional error
    if (feet && inches) {
        NSLog(@"you are %@'%@\"", feet, inches);
    }
}

This will output nothing, of course. Because of the typo height[@"inch"], the inches variable will be nil and the check will fail. Without the check, it won't crash, but it will output you are 5'(null)".

The semantics of nil in Objective-C significantly effect the design of the Cocoa. Many classes and API use nil in meaningful ways, expecting that it will allow code paths to easily and safely short out.

Now a similar thing in Swift:

let json = "{\"name\": \"sam\", \"height\": { \"feet\": 5, \"inches\": 8 } }"
let data = json.dataUsingEncoding(NSUTF8StringEncoding)!
let person = try NSJSONSerialization.JSONObjectWithData(data, options: [])

if let height = person["height"]! {
    if let feet = height["feet"]!, inches = height["inch"]! {
        print("you are \(feet)'\(inches)\"")
    }
}

This will output nothing, too, because inches = height["inch"]! will cause the if to fail. This is actually better code in this case because the language gave me a tool to leverage the new nil semantics expressively. Static typing and optionals didn't make this any safer, but it did make it clearer.

Generics are helpful when they make code more expressive and more concise. Here's a super contrived example. Notice, because Swift is good at inferring types, it almost looks like a dynamic language here.

let names = ["sam", "troy", "steven"]
let capitalized = names.map { $0.capitalizedString }

The best thing here to my eye here is that cool little word map. Having that readily available is a huge step forward. Also, Swift knows the array contains strings and so can remove some boilerplate code.

The benefit of static typing here is two fold: 1) the IDE can reason about the types and help me write the code, and 2) the compiler can optimize the output without having to rely on message passing.

Of course, this example is probably too contrived. There are ton of map implementations for Objective-C and we've been able to do the same thing easily for years. Something like this:

NSArray *names = @[@"sam", @"troy", @"steven"];
NSArray *capitalized = [names map:^id (id name) { return [name capitalizedString]; }];

It's not super important, but my quick & dirty map implementation is below. There are many great libraries that provide solid implementations, like BlocksKit. There's even a very creative implementation that builds on KVC described on a Kicking Bear post. This implementation would be anathema to a static typing fan because it's all done with strings (e.g. NSArray *capitalized = [myCollection valueForKeyPath: @"[collect].capitalizedString"];).

- (NSArray *)map:(id (^)(id obj))block
{
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count];

    [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [array addObject:block(obj)];
    }];

    return [NSArray arrayWithArray:array];
}

Swift improves on this because the syntax is more concise with less ceremony and I didn't have to write a map function or take an external dependency to get it. That's a win. Being able to adopt a functional style more broadly, use delcarative immutability, and get a more optimized output from the compiler is worth the language change. If I can get a slight, potential boost in reliability I'll take that, too, but that by itself isn't enough.

Where you've got to be careful, I think is getting too enthusiastic about enforcing safety. Don't worry too much about stopping bad stuff, use these new powers to make code concise, simple, and terse.

I think you need to make an effort to keep the code and interfaces comprehensible, too. There's a clever function posted by Wil Shipley on Twitter for a clamp function that constrains anything that's Comparable to a range. It looks like this:

public extension Comparable {
    func clamp(interval: ClosedInterval<Self>) -> Self {
        return min(interval.end, max(interval.start, self))
    }
}

I have mixed feelings about this. It's probably my failing, but I found it a little tricky to interpret quickly. I had to really study it for a full minute or two to really grok why it works because of the generic types, but it's comparing an instance of some comparable type to a range of that type and constraining it to be within that range. The generic syntax makes it kind of ugly to my eye, but it goes with the territory. It lets you do elegant things like this:

let setting = readThermostatControl()
let targetTemperature = setting.clamp(68...78) // no, we won't cool to 32 degress

But, then you can also do kind of weird things like this:

"dog".clamp("elephant"..."giraffe") // um, elephant?

This contrived example is of course nonsense. Sane people don't write code this like. But sane people do write model objects that implement protocols like Comparable and sometimes it would make sense to use a clamp function on your model type. Most of the time, though, it will probably not make much sense and it might even be just plain wrong. So really, it probably just makes sense to apply it to numeric values only (int, double, float, etc.). Is it really an improvement over a macro CLAMP(setting, 68, 78)? I don't know, but it feels a little too clever. Probably mostly harmless, probably a fun coding puzzle to work out, but representative of the kind of slightly too clever stuff we sometimes see in Swift.

This is not to say that Swift is somehow unique; there's no language that's immune to writing tricky or just plain bad code. The point I want to make is that we get better with experience, exposure, effort, and some humility, not with "safe" programming languages or frameworks.

A skilled programmer will write great code in any language while an unskilled programmer will write bad code in every language. Don't try to make better programmers by forcing them to code in a cattle shoot, help them learn mastery of any language they pick up.

Home