Things That Bother Me About Swift

By Evan Miller

December 29, 2018

When Swift first came out four years ago, I thought Wow, cool, and went back to working on my too-big-to-fail Objective-C code bases.

I’ve started setting aside time to tinker, and this December I decided I wanted to make one-page iPhone app to replicate the simple sample-size calculator I tossed up on my website back in grad school. Originally I was just going to make the web calculator mobile-friendly. But I got lost somewhere trying to figure out CSS media selectors and how to make canvas fonts not look terrible, so instead I said screw it, I’ll learn a new language, platform, and UI toolkit instead.

The app is currently In Review, which means yes, I Am A Swift Developer, but it still needs a final stamp of approval from the relevant bureaucracy before I can tell you what the app is named, and what it does exactly, and just how proud I am of my app icon.

Anyway, Swift has been written about a ton, and people smarter than me have been writing Swift code for many thousands of hours. So I’m not going to review the language in full, or even in miniature, but rather point out some things that jumped out at me while reading the language guide and while writing and attempting to compile a small iPhone app with a slightly numerical focus. Maybe someone can come along and tell me that my complaints are dumb or unreasonable, but what follows is a list of daddy long-legs currently crawling around in my oversized He-Man Versus Skeletor pajamas.

The criticisms apply to the current version of Swift, version 4.2.

Swift’s type inference fails mysteriously. This function won’t compile:

func pooledStandardDeviation(p: Float, delta: Float) -> Double {
    return sqrt(p * (1.0 - p) + (p + delta) * (1.0 - p - delta))
}

It returns the error:

error: the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
    return sqrt(p * (1.0 - p) + (p + delta) * (1.0 - p - delta))
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(Two whole variables of the same type being added and multiplied, what could their types possibly be?)

Actually, you can fix the error by wrapping the whole call to sqrt in a Double() constructor, so this compiles quickly:

func pooledStandardDeviation(p: Float, delta: Float) -> Double {
    return Double(sqrt(p * (1.0 - p) + (p + delta) * (1.0 - p - delta)))
}

Type inference is hard, I get it, but the provided error message (“try breaking up the expression into distinct sub-expressions”) is a workaround only in the same sense that sailing around Cape Horn is a shortcut from New York to San Francisco.

Productive software development requires, Victor-ian playgrounds to the contrary, compiling and running a program in your head before you fire up the real thing. In light of the above example, I lack even the vaguest of mental models as to what sorts of type inference Swift considers to be tractable.

Swift’s switch statements don’t allow empty clauses. If you switch on an enum (or anything) you have to cover every case. This makes less sense for Swift than for something like Rust, where statements are expressions and always have to return something. But fine, okay, I can deal. Now watch this code fail to compile:

var a = 10
switch a {
   case 1:
	print(“One”)
   case 2:
        print (“Two”)
   default:
        /* Do nothing */
}

Delight awaits in the form of:

error: 'default' label in a 'switch' should have at least one executable statement

So Swift requires you to have a default case — and then won’t allow that case to be empty! How are you supposed to do nothing in every case but 1 and 2? Write instead


default:
    break

Ah, a pointless default statement, and a pointless break, that combined, generate absolutely no machine code. Almost makes me miss exceptions in Java.

Swift’s Optionals are Equatable. I guess this saves you an unwrap, sometimes, but it leads to weird inconsistencies.

This compiles:

var optionalInt: Int? = 10
if optionalInt == 10 {
    print(“Ten is equal to ten”)
}

This doesn’t compile:

var optionalInt: Int? = 10
if optionalInt > 5 {
    print(“Ten is greater than five”)
}

How’s that again?

Apparently, in the olden days, Swift’s comparison operators (> >= <= <) used to take Optionals, but that was even more confusing when it came to comparing to nil, so they ripped out those implementations. (Is five greater than nil? Philosophers of language are divided.)

Swift’s closure syntax is in-sane. It’s no C block syntax, I guess, but using the in keyword — which is already used for range-iteration, as in Python, etc — to separate the function signature from the closure body is one of the strangest syntax decisions I’ve seen this side of Brainf*ck. This is what a closure looks like:

{ (let a: Int8, let b: Int8) -> Int8 in /* <- what the funny? */
	return a + b
}

Huh? How is the closure signature, in any meaningful sense, “in” the statements that form the closure body?

It’s possible the designers at Apple were smarting from dangling-caret criticism of their… innovative C block syntax, and wanted to move the signature inside the braces (fine), but in is a confusing and meaningless preposition to use here.

Swift’s unary minus can fail. If an integer operation will overflow, Swift will produce a run-time error. Protecting the children, I get it. Swift offers replacement infix operators if you want overflowing behavior: &+ is the overflowing version of addition, &* is overflowing multiplication, and &- is subtraction where your carry flag runneth over. Hooray and huzzah. But.

Signed integers, at least in the two’s complement computational world we inhabit, do not represent a symmetric range; 8-bit numbers can represent anything from −128 to 127, 16-bit from −32,768 to 32,767, and so on. So this will fail:

$ swift
> -Int8.min
error: repl.swift:5:1: error: arithmetic operation '0 - -128' (on signed 8-bit integer type) results in an overflow

It would overflow, back to itself incidentally. Okay. But you can’t do this either:

$ swift
> &-Int8.min
error: repl.swift:4:1: error: '&-' is not a prefix unary operator

Dag nabbit. What should I do if I want a non-failing unary minus?

If Swift is going to have overflow operators, it ought to cover the field, I think. I mean, if you’re going to be pointlessly exhaustive with your switch statements, shouldn’t you be usefully exhaustive with the design of your overflow operators? Wouldn’t you have to agree with that statement, Mr. Swift Designer?

***

Okay that’s all that comes to mind. Swift has been a convenient language for my humble little project, and I find the total absence of Monty Python references in the language ecosystem to be extremely refreshing. Swift is faster than Santa Claus in my numerical tests, at least once I turned on optimizations, and for me it was a major lifestyle upgrade compared to Objective-C. But if the world were perfect, there wouldn’t be anything to write about, now would there?

Actually since I started writing this, my app was approved by a kind and benevolent App Store bureaucrat, so what you thought was a language rant was actually an extended advertisement for A/B Buddy, your one-stop shop for properly sized A/B tests, written in 100% Swift 4.2. Pre-order today, and it will automatically download to your iPhone or iPad on January 15.

Now if you’ll excuse me, I have some CSS media selectors to try and figure out.


You’re reading evanmiller.org, a random collection of math, tech, and musings. If you liked this you might also enjoy:


Get new articles as they’re published, via LinkedIn, Twitter, or RSS.


Want to look for statistical patterns in your MySQL, PostgreSQL, or SQLite database? My desktop statistics software Wizard can help you analyze more data in less time and communicate discoveries visually without spending days struggling with pointless command syntax. Check it out!


Wizard
Statistics the Mac way

Back to Evan Miller’s home pageSubscribe to RSSLinkedInTwitter