Swift: Not quite there, but too far gone too

So this is a list of things I don’t like about Swift based on things I’ve read in the book, without having experimented with the language myself. This is all terribly unfair, but I write it only because Swift seems so close to great, that it’d be a shame to give up now.

I write it as someone who’s been avidly reading about languages the last year like Rust, Erlang, F#, Haskell and others, looking for the one that’d be worth learning next, without yet making the leap. In general, I’ve reached the stage of my programming life where I’d like languages to maximise correctness for a given level of performance.

Not quite there

One approach to maximise correctness is to minimise side-effects by enforcing immutability, allowing one to reason clearly about code. Erlang and Haskell are entirely immutable, whereas Rust and F# are immutable by default, but allow mutable values via a syntax that encourages programmers to think why they need mutability in the first place.

On first glance, Swift seems to start well, with var and let keywords. But then things fall apart

  1. The language is mutable by default, so it’s likely the average program will feature more mutable variables than it needs
  2. This is particularly the case when destructuring tuples and enums. It also reads horribly. The keyword var abbreviates “variable”, an adjective. The line case (x : Int, var y : Int) reads much more nicely (even if you read it as “varying”) than the clunky case (let x : int, y : Int) which we’re stuck with.
  3. The behaviour of mutability when declaring or destructuring is not consistent with function declarations, where variables are immutable by default[0]

The two places where the treatment of mutability fails horribly are with structs and lists.

First structs. It’s been well-known for over six years now in the C# world that the combination of mutable structs with value-semantics is a terrible idea[6]. Mutability gives the illusion that changes made are persisted, while the value-semantics mean you may be mutating a throwaway copy.

As an example, consider the following
[2014-06-05 10:57 Update: As explained in this reddit thread neither the dictionary nor the property example holds, as both return const values essentially.]

struct Screen {
var height : int
var width : int

i3Dim = Screen(height: 480, width: 320)
i4Dim = Screen(height: 980, width: 640)

var screens = [ "iPhone 3GS":i3Dim, "iPhone 4":i4dim ]
screens["iPhone 4"].height = 960

let i4height = screens["iPhone4"].height // equals 980

What’s happening here is that value-semantics mean we put copies of the two structs into the dictionary. When we access a value of the dictionary by key, those same semantics mean we get another copy back. We mutate this copy, but since it’s a copy, the change gets lost in the ether. In the last line, when we look for i4height, we get another copy of the original struct, with the wrong height.

The same non-obvious paths can get triggered via class properties which return structs, (e.g. phone.screen.height -= 20 is essentially a noop).

Even if a good coder avoids this, the stack allocation means a less adroit and over-eager coder might choose to change a class to a struct for some “performance” reasons, and if they do so then you can only hope a mutating method exists, as otherwise the compiler will not notice the host of bugs introduced.

Personally I don’t like the idea of structs having value-semantics and classes having reference-semantics, as it means when you see this code.

var f = MyObject()
f.propertyName = "BazFud"
let changePropertyNameToFooBar(f)
println ("\(f.propertyName)")

you have no idea what will get printed without looking at the definition of MyObject, making code impossible to reason about in isolation. I’d prefer an alloc keyword for heap-allocated objects; but given the likely complication, I could accept structs being stack allocated and passed by value if they were also immutable. Apple could simply add another initialisation syntax to the language

let f = MyObject(propertyName: "BazFud", otherPropertyName: "WhoFar")
let g = f with .propertyName = "FooBar"

In this case it should be possible for the compiler to substitute an unsafe in-place mutation of f when it detects that f is not referenced after the assignment to g. (Incidentally, it’s a pity classes don’t get member-wise initializers).

I can guess that mutable structs exist for C interop, but if that’s the case, I’d at least prefer that some sort of @Unsafe annotation was required around such mutable structs, and any code block that used them, to deter abuse.

This pales in comparison to Swift’s implementation of a list however, which is astonishingly bad. In fact it’s so bad I wonder have I got this right, so let me know. But as far as I can tell

  1. An array accessed by a mutable variable (var mutArray = ...) is mutable
  2. An array accessed by an immutable variable (let immutArray = ...) is immutable
  3. Except that you can still make changes that don’t affect length (immutArray[3] = "Whoopsie")
  4. Arrays and dictionaries are passed by value with deep copies of both. However the Swift book goes on to say that Array.copy() performs a shallow-copy, before finally adding you might want to call Array.unshare() to ensure you have an unshared array. Messy complication like this indicates a bad design.
  5. To mitigate the cost of shallow array copying , copy-on-write is used for arrays. But this follows the same rules as in (2) & (3) for immutable references, so you’ll force a copy by writing arrayParam.append(42) but you’ll mutate the array in the calling code by writing arrayParam[3] = 42

Presumably all this is motivated by performance needs, but even so, this is properly awful, and quite astonishing. It looks unfinished, frankly

Most programmers would understand if arrays and dictionaries were classes passed as reference – arrays have generally been passed by reference anyway — or, conversely, if they needed an inout annotation to be passed by reference. I’d prefer the latter, as the syntax would make the risk of mutability explicit in the function-calling code, an immutable arrays can of course be harmlessly passed by reference. But this approach is worse than either pass by value or reference, as it promises value semantics, then fails to guarantee it.

Mention of inout leads to my final knock against the language’s treatment of mutability. It requires explicit annotation of mutating methods of a struct but not of a class. Lets say however, that Swift 1.1 made mutating requirement of class methods. One could therefore require that an inout annotation was required for a class mutator to be called in a function. This would make mutations clear in calling code

var c = MyClass()
var d = MyClass()
fooBar (c) // c is not mutated here
bazFud (&d) // d is clearly mutated here

Of course this is overloads inout references slightly, since it also allows d to be replaced by an entirely new class instance, whether d differs due to a mutation or a replacement, the fact is the d instance you had prior to the function call is not the same as the d you have after you’ve called it.

Rust is the only other language I’ve seen that’s tried to unify explicit treatment of mutability with both pass by value and pass by reference semantics, and it manages to square the circle, albeit with a lot of syntax.

But too far gone

Rust is a very cool language, with an impressive type-safe approach to memory-management and mutability which guarantees correctness at the performance of “idiomatic C++”, which is to say C++ using automatic pointers. It’s not quite as codeable as Swift, F# or Haskell due to the need to annotate variables with ownership markers, particularly when deconstructing enums, and so for the average coding job, the performance gained may not justify the more complex development environment. Nevertheless, it’s clear its authors have put an impressive amount of thought into correctness.

It’s also clear that the developers of Swift have been following Rust, and have been very impressed themselves. Following Rust they’ve added support for ADTs in the form of enums, traits (i.e protocols) and type-aliases and explicit mutability. I actually quite like this style of coding, it forces you to create small, easily composeable data-structures, instead of the deep tangled hierarchies OOP language often inspire.

The problem is Swift also keeps classes and structs. Thus Swift is a multiparadigm language: developer Alice may write code using the style implied by the library, using classes and protocols, whereas developer Bob, an avid reader of HackerNews, may use ADTs everywhere in the search for correct-by-default self-documenting code. Once Eve joins the team, and has to link these together, she’s going to have a bad time.

The best example of this is in the very first chapter of the Swift book where they use an option type as an example

enum OptionalValue {
case Some(T)
case None

And then proceed to use the optional operator throughout the rest of the book. Fortunately the operator is well thought out, so like the type, it forces you to consider both possibilities every time[2]. The very fact that an example in the first chapter of the book proves to be redundant however, should have been a hint that something was awry with this hybrid approach.

The same is true of mutability and ownership. I’ve mentioned mutability above, and I’ll leave the ownership issue to the chapter of the book, but the treatment of these suggests a desire to emulate immutable or immutable-by-default languages without fully following through with the consequences. Again, it falls in a muddled middle, instead of definitively taking one side or another. Personally, I’d have followed the examples of Rust and F# and have the language enforce immutability by default[3]

On a more cheerful note, the pattern matching support, which also looks inspired by Rust, is very well done.

And then some

There are a few little niggles here and there as well. The language has all the features in place for list-comprehensions (or LINQ if you prefer SQL syntax), right down to tuples with named components. Yet nowhere does it occur.

Also why does one use the static modifier with structs and enums, but a syntactically separate but semantically identical class modifier with classes? Why not use the same modifier everywhere?

And why is override a keyword when @final is an annotation – shouldn’t they all be annotations? Both can trigger compiler errors.

Speaking of core language features, making = a statement is a great way to eliminate typo-induced bugs. Supporting both .. and ... is a great way to introduce them. Writing 1..(count + 1) is not that much of a hardship, surely.

Why embrace the full insanity of arbitrary C++ style operator overloading, when you could simply define certain protocols such as Equatable, Comparable, Number and ScalarNumber, with named methods, and have the compiler allow operators to be substituted in place of method names for implementing classes.

And while we’re talking about protocols, some like Equatable, Comparable, Printable, Hashable etc.[8] are such a core part of modern languages, that modern languages like Rust and Haskell provide annotations that will create automatic implementations for you. Why in 2014 does Swift ask you to write a hashValue() method from scratch.

As the language uses an option operator instead of an option type, the type system doesn’t guard against accessing a null value, so null pointer exceptions / segfaults / call them what you will are still possible if you’re careless with the ! operator. Using OptionTypes would fix this of course, but I’ve argued against ADTs in a language that also uses classes. So it can’t be fixed, but it’s indicative of how the link to Cocoa has limited Swift: it’s not quite the HFS+ of languages, but it’s design is a lot more compromised than Haskell or Rust say.

Speaking of being shackled to the ancient past, it’s disappointing to see assertions, introduced in C in the seventies, are still the best Swift has to offer, and with literal error messages too. Java at least offers programmatic error messages in assertions (i.e. you can include the failed value in the error message), and other languages such as Eiffel, Oxygene and Ada offer full contracts with “requires” and “ensures” pre and post conditions. Speaking of the Pascal languages, range types are also a good idea never that never seemed to get wider attention (e.g. “`type DayOfMonth = 1…31; let d : DayOfMonth = 3“`). Translated into runtime assertions, they’re a long way from dependent types, but still help make bugs shallow and code self-documenting.

Finally there’s error handling. I’ve never been convinced by opponents of exceptions, as I’ve never seen a program made more readable by enveloping every line of code in three lines of if-statement and handler — particularly since such handlers often call goto. However from what I gather[5], the Swift way is to return enum types similar to Go, Rust and Haskell. I accept I’m in a minority here, but it’s curious that the language guide has so little to say about this

 So what to do?

For Objective-C coders, used to slogging through thickets of stars and braces this must be revelatory. And of the popular modern languages, this is probably the fastest[1] out there to have a solid SDK, IDE and all the other trimmings. It’s a pity it’s not managed to exceed C# however.

Were it immutable by default, with immutable-only structs, and methods that required explicit mutating annotations, it would be easier to reason about. If the inout modifier was required for functions calling class mutators, it would be easier to reason about still. If .. replaced ... a whole host of typo-induced bugs might never happen. If the semantics of the map and list implementations were rationalized, then the manual wouldn’t need so many paragraphs of ifs, elses and buts. In fact if Array were a class in an immutable by default regime with explicit references, such dodgy copy-on-some-write semantics wouldn’t be necessary.

Of necessity, it’s forever going to be chained to classes and protocols. But Apple could still drop ADTs (i.e. enums with associated data) to create a consistent single-paradigm language. This would require rethinking error handling, which isn’t easy of course, and as an exceptions fan (and checked exceptions at that[7]) I know how opinionated people are about error checking.

And it has to do it all by September…

Which is huge, but this language, once released, will be fixed for a decade at least. Something with that lifespan should be great from day one. As for me, I think I’ve talked myself into trying Haskell, while keeping an eye on Rust. I may never work professionally with it, but I think I’ll learn a lot about different ways to structure code.

[2014-06-03 21:40 Updated to add dictionary example for structs]
[2014-06-03 21:58 Added note about the fact I’m “overloading” inout]

  1. Yes, yes, an immutable variable is an oxymoron, but you know what I mean

  2. For those who are curious, Swift is the second language to pull off this trick. Objective C is not the only C-with-objects language to get wrapped in a C# shell. The open-source C/GObject/GLib combination that underpins the Gnome desktop was wrapped in a language called Vala in 2006, also mimicking the C# syntax, also using reference-counting for memory-management, and also adding in a “weak” keyword to handle cycles.

  3. Note that an option type would always require pattern matching to handle, and so it is _impossible_, thanks to the type system, to access a null value. The option operator, not being restricted by the type system, allows one to access a nullable variable directly by means of the ! operator, and so, unlike Rust and Haskell, null pointer errors still exist in Swift.

  4. An interesting aspect of wholly immutable languages like Erlang is that in addition to simplifying concurrency, the fact that objects can only point to older objects (as one can’t retroactively change a pointer) means that heaps are unidirectional, and so one doesn’t need to bother with the complexity of weak pointers.

  5. Footnotes in WordPress proved far too complicated for me to remove this one…

  6. See the server response example at https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html

  7. http://blogs.msdn.com/b/ericlippert/archive/2008/05/14/mutating-readonly-structs.aspx

  8. So I’m an uncool checked-exceptions dweeb. As I said before, people talk about error checking with if-statements, but if you do it properly, and wrap every unreliable call in a 3-line if statement, your code becomes unreadable. If-statement people already use goto, so why not just formalize it with exceptions. Next, if a call can fail, then the calling code shouldn’t pretend it won’t (bugs, after all, are things that don’t work when we expect them to) so checked exceptions are useful. Further the syntax is self-documenting. Done badly people complain that they’re checking millions of exceptions – but this isn’t due to the mechanism, it’s due to the people using it. Exceptions should exist in an abstracted hierarchy like everything else, so each function should only raise one or two exceptions, describing its failure, not the root cause. If it’s not directly responsible, then the exception it raises should wrap the true cause. And of course, all of this is avoidable if the crazy outside world is neatly contained in a nice secure Monad…

  9. I’ve made a lot of these up, as the docs are a bit hard to navigate at the moment, but you get the idea

So who am I?

I’m pursuing a PhD in topic modelling here in London, with a focus on short texts (e.g. tweets and image captions).

Prior to that I worked in companies writing recommender systems, online document classifiers for proxies, and virus-checkers for proxies.

Prior to that I did a Masters in Machine Learning.

Prior to that I was in Ireland, working with the Cancer Registry on a bunch of jobs, from web-design to record-linkage – the latter is what inspired me to up sticks and got to London to learn more about machine learning and data-mining as it was then called.

And prior to that were fun times in school and uni in Cork.

I’ve no idea what’s next.