Add Type Narrowing to Swift

TypeScript really does amaze me with how much it’s managed to bring order to the organically-grown, deliberately-permissive, and ad-hoc overloaded APIs of the JavaScript ecosystems, some of them more than 20 years old. Type narrowing supports the idioms that, for better or worse, have emerged as the, well, idiomatic way to define and use such APIs. And the fact that they’ve managed to do so performantly is a marvel.

But C and Objective-C and Swift APIs usually aren’t like that. C and Objective-C don’t have overloading, but their type systems are strong enough to discourage the sort of ad-hoc overloading JavaScript does instead (and by extension TypeScript).* Swift does have overloading, and doesn’t need to do it ad-hoc at all.

As people have noted, the two main uses for narrowing that apply to Swift are checking Optionals and downcasting. In TypeScript these are both narrowing operations because both are usually represented as taking an anonymous union type and ruling out certain arms of that union. On the flip side, TypeScript tries to preserve JavaScript idioms, so having to convert if x === null to guard const x else would mean fewer people would adopt TypeScript. But neither of those apply to Swift, because Swift did not need to stick as closely to existing syntax, and because Swift doesn’t have anonymous union types. Instead, Swift encourages protocols for factoring out common operations across disparate types, rather than type-switches. That’s a choice with trade-offs, to be sure, but it’s more feasible in Swift than JavaScript because extension methods on Swift types can’t collide at run-time.** So no “prototype pollution”.

Of course, Swift does still think this is useful for Optionals and for enums in general, which is why if let/guard let and switch exist. And to be fair, TypeScript’s system can represent some checks that Swift’s can’t: if x.y !== null has no direct equivalent in Swift for further uses of x, and similarly there’s no way to represent the resulting type X & { y: Y } (rather than y: Y | null) in today’s Swift. But on the flip side, Swift lets you add methods and protocols to the built-in String and Int types, so there’s much less need for common unions like string | number.

* At least with the way Apple has used Objective-C. The original ObjC had sensibilities closer to Smalltalk’s and thus to JavaScript’s, with id-typed parameters and a general expectation of “duck typing”; if NeXT had stayed in that vein, we might have had a different world. But even duck typing is closer to TypeScript’s interfaces than its union types.

** The exception is methods exposed to Objective-C, whose dispatch model is closer to JavaScript’s.

19 Likes