A language and its design have profound consequences on its users. As a bilingual person, I have the benefit of being able to think through a problem in both my mother tongue as well as in English where I can take advantage of small differences between those languages. (For example, one language may have a very specific word for a concept while the other differs in grammatical structure that allows me to see the problem from a different angle.) Perhaps the best example of the impact of the design of a language comes from research on Magnetoreception (the perception of the geomagnetic field) which discusses languages throughout the world that lack words for egocentric directions (front, back, left, and right) and instead solely rely on cardinal directions (north, south, west, east).² The users of those languages have become extraordinarily aware of their sense of direction. Perhaps imagine if you had never learned the word for blue—how would that affect your perception of the world? There is even evidence that a higher percentage of tonal language speakers (such as Mandarin and Vietnamese) have absolute pitch — the ability to label the musical pitch of an arbitrary sound — than those who are not speakers of tonal languages.³ The design of a language has immense power to affect its speakers and their way of perceiving the world.
An influential linguist Edward Sapir once wrote that “The worlds in which different societies live are distinct worlds, not merely the same worlds with different labels attached.”⁴ I believe this also apply to us as software engineers with programming languages: it is important for us as developers to invest the time and effort into learning unfamiliar programming languages.
For example, let’s say that you’re a junior iOS developer and have only ever read or written Swift, and never Objective-C. There are programming paradigms that you’re missing out on.⁵
Objective-C and Swift
One of the key differences between Objective-C and Swift is how functions are called. Objective-C implements its functionality by passing runtime messages between objects whereas Swift function calls must be known at compile-time. This has a few serious implications: it’s possible to compile and execute an Objective-C program where the object sending a message (i.e. a function call) doesn’t know anything about the recipient of that message. The opposite is also true: the object receiving a message may not know what to do with that message (i.e. missing function declaration). This means that in Objective-C, you don’t have to know what function call (i.e. message) your code is going to make when you compile your app. This messaging system (which can allow for a lot of abuse) enabled and continues to enable an incredible number of iOS and macOS applications and libraries that would not have been practical or even possible in Swift. (This is how Dropbox placed those green checkmarks in Finder before the official API became available in OS X 10.10 “Yosemite”).
Here is another example of what you’re missing out on if you’ve only ever written or read Swift:
Until recently, I had never used the
await pattern. While I am intimately familiar with concurrency and the immense power of Grand Central Dispatch, I completely missed out on
await (and Promises!), I realized that I had done something similar many times in the past in Swift using Grand Central Dispatch's
DispatchGroup. However, the key difference was that Swift does not have language-level support for the
await prevents the execution of the code from continuing past the
await keyword until the
async has been resolved.) This has the power to enable a meaningful order of asynchronous operations as well as avoiding potential callback hell that can arise even in the best of Swift codebases.
Let’s look at an example:
Here is one (quite suboptimal) way in Swift to execute a similar operation only using
Clearly, this is why libraries like PromiseKit exist. (At Livefront, we’ve written a thin wrapper around
URLSession that allows Promise-like chaining of tasks and error catching for network operations.) However, neither of these solutions enable the synchronous design that
await provides should it be needed.
“The worlds in which different societies live are distinct worlds, not merely the same worlds with different labels attached.” - Edward Sapir
await. Now that I’m familiar with this kind of control flow of combining asynchronous and synchronous code together, I have yet another tool in my toolbox that I can use at a moment’s notice in problem-solving. And, even though I can’t natively use
await directly in Swift, I can borrow concepts from
await to implement new idiomatic solutions.
If you’re saying to yourself “but I already knew about async & await!”, that’s awesome! And, you’re right:
Keehun loves to
find.out().then(obliterate(his.ignorance)) at Livefront.
⁵ If you are reading this, and you are a person who has only experimented with a single language (Swift or any other), I highly recommend you go check out other languages such as C++, Rust, Go, Java, R, C#, and Kotlin.