Impressions of Apple’s Swift, after a bit of practice

Lately I’ve been getting my feet wet learning the latest new programming language from Apple that everyone is talking about: Swift. The short conclusion is that it’s pretty good, but unsurprisingly has quite a collection of glitches: some of them are fixable, some of them are annoying arbitrary choices that I’ll get used to, and others are probably more like design flaws that are unlikely to ever go away. I’m cautiously optimistic that the latter category is fairly short, though still too early to call it. And because Swift is such a new language – hot out of the oven, as it were – I was not inclined to rush in and start coding up anything that’s directly important to my company’s cheminformatics software stack, so instead decided to take a little detour and make a new app from scratch: something fun, that I’ve been meaning to do for ages, called the Beer Lab Notebook, written entirely in Swift, with no non-Apple dependencies. But more about that particular project in a later post.

The first the reason why Swift is interesting is because Apple seems to have bet the farm on it. Until a few weeks ago they had been spending years forcing developers to do most or all of their Apple-specific coding in Objective-C, and while there must be a few afficionardos out there, it really is a wretched dead end of a language. Despite its inadequate origins, Apple put so much work into polishing the proverbial turd, and encapsulating it with proverbial layers of gold plated foil, that the overall experience is only slightly more unpleasant than using a well designed modern language, but one does occasionally think how much better it would be if all of this energy were applied to something that was actually good. Hence switching to a language which actually seems to be exactly this is a case of not a moment too soon. Given Apple’s tendency to aggressively push their users and developers into the latest versions of everything, it’s probably a good idea to plan to get with this program sooner rather than later.

After having read through the language overview, there is one thing in particular that stands out about the language: it is quite exquisitely balanced in an effort to have a concise, clean syntax that effectively expresses not only what you will do with a block of code, but also what you won’t do. This is an incredibly important design feature of a programming language, which most fail to do, either by going too far or not far enough. Back in the day, the choices were between scripting languages that typechecked and memory allocated everything automatically, which were slow and impossible to optimise; and there were low level languages like C/C++ that required you to manage everything yourself, and hence were incredibly slow to code & debug, because even the most trivial task was inherently dangerous. Then came languages like Java, which wrapped everything up in omnipresent exceptions, bounds checking and garbage collection, which was a huge improvement: depending on the coding style, rate limiting steps could often be brought to within 2-3x of C-like performance, with even more protection than a scripting language, thanks to the strong compile-time typing system. But these days, Java is showing its age: while it got more things right than wrong, and has improved a lot over the years, there are a lot of mistakes that have too much momentum to fix, and one of the ones that bothers me a lot is that there are too few ways to shut down the garbage collection, which leads to awkward coding. Trying to use as few classes and arrays as possible, and avoid duplicating non-primitive objects, is effective but it requires a significant level of care that would be nice to spend elsewhere. While I’m no expert in just-in-time compilers, the reality is that every time you use the new keyword in a rate limiting block of code, there is real worry that your inner loop that needs to be called a billion times in a row will start thrashing the system unnecessarily.

Later on when Microsoft gave up its attempt to steal Java and invented C# instead, it seemed to me like it should have been called “Java 2.0”: it had almost all of the same ideas and positive capabilities, with a great many of the flaws corrected, e.g. the introduction of the struct, which is like a class, except allocated like a primitive object, and bringing back pass-by-reference. I haven’t looked at the platform for more than a decade, so I have no idea how well it has held up, but my first impression of Swift was a bit surreal: from the point of view of the features that I have been missing the most, it looks like “Java 3.0”.

One of the most noticeable characteristics of the language is that it seems to have been designed with the #1 priority that trumps all other concerns being memory management. It seems that the designers made the decision that objects must work perfectly with automatic reference counting, at close to zero cost of allocation/deallocation, and leave the programmer to be all but completely oblivious as to when anything is deallocated or duplicated-on-write, and all other considerations must be subordinate to this. Combined with a very graceful approach to constants and scoping (e.g. the var vs. let syntax) it’s a great way to encourage programmers to intrinsicly lock down the degrees of freedom for how the compiler interprets their lines of code. This is a very fundamental idea: you want to be able to let the compiler know that you won’t be doing certain things, which has two consequences: (a) the compiler can then optimise only for the things you can do, and (b) if you try to violate this covenant, the compiler will shut you down with an error: it will never be turned into a binary, and never get executed. With a dynamic language like JavaScript, the optimisation of (a) is by definition not possible; with a language like C, violating (b) means it will likely just go ahead and core dump at run time (and that’s if you’re lucky!)

So that’s the promise, and the thing that I took away from my first reading, several months ago. What about the reality?

For someone who is accustomed to the C-derived languages, the syntax is a bit awkward. While some of the keywords fall into line naturally (e.g. var & let), there are a lot of oddly named conventions, e.g. the use of “class func” for static functions within a class (which is a rather confusing choice, since you can define classes inside classes, too), and the strange way of defining constants (they have to be part of enumerations). The way in which function arguments can be either named or anonymous seems like a nice idea, but the syntax is initially confusing, and there are some stealth holdovers that are obviously for compatibility with Objective-C libraries, and it’s hard to imagine that such design features would have been contemplated if the language had been designed from scratch.

One of the arbitrary decisions was taking out the need to terminate units of code with the semicolon, which seems like a nice idea: but this has the unpleasant side effect of making it always necessary to surround an if/while/for clause with curly braces, whereas in other languages it is optional for single execution units. For certain coding styles this probably makes little difference – some people do it anyway even though they don’t have to – but for those of us who prefer to be a bit more concise when it doesn’t hurt readability to do so, this is actually a major nuisance, as it forces ugly code. Another problem is that the compiler is much more whitespace sensitive than its contemporaries, partly due to some new operators. Again, this probably only affects certain coding styles: for example, I prefer to write “if (a!=b)” whereas the majority of programmers seem to prefer to add a bit more padding, e.g. “if (a != b)”. The first clause actually fails to compile in Swift, because the “!” symbol right after a variable has a meaning all of its own. My complaint is probably in the minority and will not be heard, but it is one of those problems that should have been designed out from the beginning. There seems to be some scope for making the compiler smarter, but I think much of the extra whitespace sensitivity is here to stay. The annoying thing is that the workarounds are just enough of an eyesore to upset my perfectionist tendencies, but not quite hideous enough to force me to conform to somebody else’s arbitrary aesthetic standards.

One of the problems that is likely temporary is the quality of compiler error messages. Having been using mostly Java and Objective-C for the last few years, both of these environments have very mature compilers, which have gotten very good at analysing what’s wrong with broken code, and generating a very helpful compiler message that gets straight to the heart of what is really wrong, and sometimes even offering helpful suggestions as to how to fix it. The latest Objective-C compiler is particularly impressive in this regard, but the version 1.0 of Swift is nowhere in that same ballpark. Compiler errors are not as bad as with a heavily overloaded macro’d and templated C++ build system – at least the messages are short – but often the content is not much more useful than “something is wrong with this line”. But I imagine this aspect will improve really fast over the next couple of years.

Over the decades I’ve learned a lot of new programming languages, and the way it usually works is that one must learn a new language syntax and a new set of class libraries. Getting up to speed on both is equally important. With Swift, though, the class libraries are the same as for Objective-C: all the same old classes are there, just with a different invocation style. The low level functions are mostly the same as with C. There are a few extra Swift-specific functions, but they seem to be few in number, and are mostly functional operators of one sort or another.

There are plenty of miscellaneous things to get used to. Backwards compatibility with the legacy class libraries that also work with Objective-C is presumably going to be phased out over time, and I have no idea what the replacements will look like. The use of generic types seems to be incredibly flexible and powerful: I never much got into them when I used to like coding in C++, but in Swift things tend to work rather well, though I have yet to do any real benchmarking.

The new native String type seems to have finally made the jump between the long standing dichotomy: the idea of a string as an array of characters is an incredibly useful construct in computer science (e.g. when reading in ASCII-formatted datastructures from a text file, among other things), but they also need to be used to encode a graphical representation to show to humans who read many different scripts, not all of them even based on an alphabet. Swift leans way hard toward the second use case, and what has been sacrificed is the convenience of string handling. Try to figure out how to find the numeric index of a symbol within a string: good luck with that. In fact the language doesn’t even seem to have a way to encode character literals at all, only strings. So far it is easier to cast back to the NSString class and regain the missing functionality, because it’s hard to believe the highly contrived string manipulation functions don’t come at a heavy performance cost. When I’m writing code to parse an MDL Molfile, I tend not to be too worried about encoding an emoji, or some kind of backwards-running hieroglyphs.

At this point, I’m still a newcomer to the language, though I have built an entire app from scratch with it, and started porting over some fairly sophisticated cheminformatics libraries, and for the most part so far so good. I’m fairly sure that the language is here to stay, and that means that Apple will keep refining it at a rapid pace, for a long time. It still has quite a few rough edges, but even with the hacks and workarounds and unhelpful error messages, it does, actually, seem to be ready to get the job done.

Leave a comment