When Swift was announced in 2014, I was as surprised as anyone. There were only a handful of folks working at Apple who had advanced knowledge of it, but like the rest of the Apple development community we were intensely curious about the new language. After a quick investigation, I decided my work on iTunes Connect for iOS wouldn’t benefit and I contented myself to allow it to mature.
When I decided to leave Apple this summer, I realised it was genuinely time to learn Swift. From all accounts Swift was finally ready for Prime Time with ABI Stability and no more source-breaking changes. I’ve always picked up new languages in the past by building something with them. Caitlin and I had been brainstorming an idea for a new App for quite a while; so this would be an ideal opportunity. I dove right in.
First let me say I am extremely grateful for the patience and tolerance of the Seattle Xcoders. I’m actually a bit surprised they haven’t banned me after all my complaining. As a software engineer with more than 30 years of experience, learning Swift is not like learning other languages. In Swift there is often One True Way™ and to deviate from The Way means frustration at best if not outright failure. This often shows in small things like familiar patterns being missing, because as Daniel Steinberg jokingly puts it, “You can’t be trusted with it.”
This weekend I tried converting a tool from Ruby to Swift — largely to be able to take advantage of CoreGraphics without jumping through a lot of extra hoops. (Plus it would help expand my understanding of Swift.) The first feature I started working on was the DependencyList
which tracks what files have changed from one execution to another. It’s nothing complex, but essential to efficient operation.
As you’d expect the DependencyList
keeps track of whether there are changes. As new files are scanned, the @changes
property is updated as follows:
if old_dependency
@changed |= (old_dependency.digest != new_dependency.digest) || (old_dependency.mod_time < new_dependency.mod_time)
else
@changed |= true
When I tried to replicate this same logic in Swift, I ran up against an interesting road block. The |=
operator isn’t valid for boolean values. For example:
if let oldDependency = oldDependency {
self.changed |= newDependency.hasChanged(from: oldDependency)
}
else {
self.changed |= true
}
The compiler tells me the following: Binary operator '|=' cannot be applied to two 'Bool' operands
. I’m surprised this isn’t permitted in Swift, which to my eye seems to value brevity over clarity in many cases.4
It’s not a big thing to use foo = foo || boolean-expression
instead of foo |= boolean-expression
, but one of the things I’ve always taken for granted about learning a new language5 is using patterns I’m already familiar with even if they aren’t the native idioms. This was very much the case when I started learning Java, Python, and Ruby. My code didn’t become idiomatic for a long time, but it worked nearly right away.
The second stumbling block I encountered was with Encodable
.6 The new Swift Encodable
/Decodable
protocols are pretty amazing. They can’t do everything, but they’re still a great step forward. I figured JSONEncoder
would make saving my DependencyList
a super simple matter. However, I ran into something… odd.
Consider the following code:
struct Dependency: Encodable, Hashable {
let sourcePath: URL
// otherstuff
}
var files = [URL : Dependency]()
let encoder = JSONEncoder()
let data = try encoder.encode(files.values)
The DependencyList
keeps track of all files its seen in a dictionary. However, as each dependency structure also has the relative path of the source file, all that needs to be saved is the values in the dictionary. Unfortunately, this doesn’t work, because as the compiler informs me: Argument type 'Dictionary
.
This was just baffling. What I’d learned in Swift so far is if you get to a point like this it’s because you’re Doing It Wrong™. But it felt wrong to loop through the values and explicitly add them to an array. The eventual solution of converting the values to an array via Array(files.values)
similarly feels wrong, but I can’t say why.
Much of how Swift does it’s thing is so deeply technical it borders on magic, but when that magic falls short, it exposes very sharp edges making learning Swift much more challenging. For those of you who started with Swift at 1.0 and have kept pace with all the idiosyncratic changes, you’re accustomed to its quirks. I suspect Swift really is a great learning language if you stick with small lessons and go step by step. But if you’re learning by doing, diving into the deep end, it may be more frustrating than you expect if you’re already a Swift aficionado.
-
One of the Seattle Xcoders helpfully provided me with a custom operator that would allow me to use
||=
instead, but I have strong negative feelings about custom operators left over from my days writing C++. ↩ -
Just for comparison, I’ve learned Basic, Pascal, Modula 2, Smalltalk, Lisp, C/C++, 8086 Assembly, Fortran, Cobol, Python, Ruby, Javascript, Objective-C, Java, C#, and now Swift. There are probably some specialised platform languages in there I’ve forgotten. And yes, I know that’s not a huge list. You probably have forgotten more than I’ve learned. ↩
-
It was at this point when my complaining was enough to frustrate one Xcoder enough that he mentioned it. I genuinely feel bad about raising his blood pressure and I’m glad we both got away from our computers to enjoy the sunshine. ↩