Programming in Swift
13 October 2021My day job has required me to look at app development rather than my usual back-end server stuff, and while I am familiar enough with Java on Android over the last week or two I have put a fair amount of effort into trying to understand iPhone app development using Swift which I did by writing a new client-side networking interface. Having not used Apple Macintosh systems much since the late-1990 it has been hard enough getting used to the idiosyncracies of the modern iMac interface having spent years using Linux — where the computer system has to change how it does things to suit your tastes rather than having to band your tastes to the computer's — so throwing in learning a new language at the same time has been a bit of an ordeal. Anyway after much confusion I sat down and read the guided tour of the Swift language itself top to bottom.
Some of what I saw made me skeptical about the prospect of Swift becoming a mainstream language, let alone one that I think is personally worth making a heavy investment in. In short I think Swift has many language design decisions that are asking for trouble when it comes to maintainability. There are both unnecessary inconsistencies and semi-redundant variations within the language grammer which makes it easier to write hard-to-read code, which I noticed from the start when trying to scout out code samples.
Background
Like much professional development my need to do iPhone development is going into an existing code-base to add and change things rather than green-field development, mainly as a way to take some load off the lead developer, so my interest here is what makes existing code easy or hard to understand. Even though the way to learn a language is to write something new this still entails reading existing sample code and extracting understanding from it based on incomplete knowledge, so anything that makes a language easy or hard to read also makes it easy or hard to get to grips with — and so far I think Swift has fallen into the ‘harder’ category. Being able to sit down with a blank sheet is something that software engineers rarely get to do, so the bottleneck is often getting an understanding of what code is already there, which quite likley was written a while ago by someone who might not even be around any more.Perl's philosophy of allowing a developer to write code in the most expedient way possible has the side-effect of making code potentially harder to go back to, so its longer-term false economy has been blown out of the water by Python which more or less forces people to write clearer code to begin with. So my interest is whether Swift is a Perl that makes writing expedient, or a Python which makes reading easier.
Trying to be different
To me it looks like there is a determined effort to make the language very different from C and its dialects with syntax being drawn from more obscure languages, which is paricularly noticable with the variable-type ordering of declarations (e.g.value : Integer
) instead of the type-variable (e.g. int value
) used by list about all the mainstream languages.
Maybe it is because the designers specifically wanted to get away from Objective-C that has been used by Apple ever since it absorbed NeXT, but it does make picking up the language that bit more difficult for people who can already program.
To make matters worse the langauge seems to have had major changes between the major versions, such as an overhaul of recoomended coding styles which to me is a sign of dogma rather than practicality, but in this case I am only interested in the latest Swift 5.
Some good bits
Swift has a concept of optionals which is a special case of Haskell's polymorphic types and it basically allows a variable to have the special valuenil
.
Much like Python's None
this is an explicit nothing value that can be used instead to signal errors without resorting to exceptions.
At first it causes all sorts of confusion because as part of optionals you see ?
, ??
, and !
suffixed to some variables but once it makes sense it is very convenient. For instance consider the following:
let date = (year.month?.week?.day.name) ?? "(none)"
If month or week are nil then the recursive lookups are skipped, and date takes on the default string of “(none)
”.
Do something like this in Python and you would end up doing stuff like catching ValueError
exceptions.
Using !
forces a value to be non-nil and makes it a run-time error if it is not so.
There is also a subtle difference between varables declared as let
rather than var
where the former are supposed to stay constant whereas the latter expect to be changed.
A bit much
I have seen a lot of syntax for loops in my time but that chosen by Swift is definately the terse end of the spectrum; however it has a interesting quirk of allowing the programmer to specify whether the counting includes or excludes the upper bound. In the snippet below bothi
and j
count upwards from zero but j
does not include the final value of 10
.
I am in two minds about this because it means the feature creep of yet more syntax to learn, but in this case having the two different variants for loops suggests that the designers had fencepost errors in mind.
for i in 0...10 { for j in 0..<10: { // ...
As well as having getters and setters, there are also willSet
and didSet
which are bits of code run before and after respectively of the variable being changed — didSet
seems redundant since willSet
also has access to the new value.
Aside from these not being run when the variable is set from a class constructor I am not sure what functionality they provide that cannot be obtained using getters/setters instead.
Over the top
When I read the section on Functions and Closures my initial feeling was this part of the Swift language being an interesting mix of JavaScript and Haskell but then it soon became apparent that the language's designers had a nasty case of overengineering. What caught my eye was function parameters being able to have different names inside and outside of the function, and that the latter can also be optional.func makeWelcome(_ name : String, time message) { return "Morning \(name), it is \(message)." } makeWelcome("Remy", time: "22:46")
Far as I can tell 99% of the time people will not use different internal and external names and the use-cases I hear for the remaining 1% to me don't justify complicating the language. Addied to this if a parameter has a default value then the external name has to be given in the function call, but it is crazy that variables have to be specified in order even if the function call uses the tag (i.e. external name) for all parameters. Things get worst with closures which are basically anonymous functions defined inline of other functions. Firstly the syntax is needlessly different from that of functions as shown in this code where the closure of squaring a value is applied to a list:
values.map({ (value:Int) -> Int in let result = value * value return value })
The obvious thing would have been to follow in JavaScript's footsteps and make it something like func _(value:Int)->Int {
rather than require developers to recognise what looks suspiciously like a deliberately different syntax.
In certain circumstances where the type are somehow already known, which in this case I am guessing is due to (Int->Int)
being inferred as the parameter for the map functon due to it being a member function on a list of integers, the parameter and return types can also be omitted:
values.map({ value in let result = value * value return result })
In the wild this omission is probably the rule rather than the exception, and I have recollections of this seeing this form without the closure ‘{
’ and ‘}
’ which is a major reason I decided to read up on the language.
Oh, and for good measure the return can also be omitted if the closure consists of a single expression.
This in itself seems fair enough since I suspect such cases are quite common and would normally be collapsed down to a single line rather than the there lines used below:
values.map({ value in value * value })
To me it looks like Swift's designers started out with Python's lambda functions and then worked backwards to make them as general as functions. However the next bit is the real clanger and this made me decide that Swift's designers are of an academic mindset of over-complicating the language rather than making it practical for real-world use:
values.map({ $0 * $0 })
Yep you can do away with parameter names and instead used parameter numbers just like in Perl. Having multiple ways of doing things lets developers write code a lot quicker, but this comes as the expense of making it harder to go back to existing code and work out what is happening. A lot of actual bread-and-butter software development is modifying existing programs and I ended up ditching Perl because it too readily invites messy unmaintainable code due to the number of different ways something can be done.