How do you color your functions?
Bob Nystrom, from the Dart team at Google, wrote a post titled “What Color is Your Function?” back in 2015. TL;DR in most programming languages functions come in two flavors. There are regular “blue” functions and asynchronous “red” functions. Whenever asynchrony in the language is expressed via callbacks or promises, still
red functions are more painful to call.
C# had pioneered a solution to alleviate this pain with async/await, which is being widely adopted across JS world, including Dart, but
you still have divided the world in two. Those async functions are easier to write, but they’re still async functions.
Bob goes on to conclude that
Go is the language that does this most beautifully in my opinion. As soon as you do any IO operation, it just parks that goroutine and resumes any other ones that aren’t blocked on IO.
Go does not color its functions at all. Any function in Go has a right to suspend its goroutine by doing an IO operation.
It’s that Go has eliminated the distinction between synchronous and asynchronous code.
But what about Kotlin?
Kotlin is a new kid on the block here. Kotlin Coroutines are halfway between async/await colors of C# heritage and color-free world of Go. In Kotlin you still color asynchronous functions with suspend modifier (instead of async), and you still cannot simply invoke asynchronous function from a synchronous one, but the way you write asynchronous code is mostly the same as your synchronous code. You don’t have to use await in Kotlin and results of Kotlin suspending functions are promise-free — they are not wrapped into any kind of
Task<T> type in Kotlin, they are plain and simple
Why stop halfway? Shouldn’t we eliminate suspend modifier from Kotlin and allow any Kotlin function to suspend execution of a coroutine, just like in Go, where, in Kotlin terms, every function is a suspending function?
We cannot eliminate suspend completely. Kotlin has to be interoperable with the JVM ecosystem where functions are blocking and asynchrony is represented via callbacks and futures. Regular JVM functions do not expect to be suspended. JVM is colored and will stay colored for long due to legacy asynchronous libraries, even if Project Loom delivers all its promises of color-free world on JVM.
We could eliminate suspend modifiers from pure-Kotlin code, but should we? I’m inclining to answer no. Having to mark asynchronous functions with suspend modifier is a small price to pay, but in return you get better insight into your code. You immediately see which functions are allowed to perform potentially long communications and which are supposed to complete quickly. Take a look at channels in
kotlinx.coroutines library. They are designed and operate very much like channels in Go. But in Kotlin you can see that
send function on a channel is marked with suspend, while
close is not, so you don’t have to read a single line of documentation to immediately see that
send may suspend if no one is receiving on the other side of the channel, while
close always completes immediately. It works the same way in Go, but here Kotlin type-system helps us better reason about the code by representing a part of documentation in the function’s type.
Of course, Kotlin lives in JVM world where any function can just do blocking IO, so writing truly asynchronous and scalable code on JVM takes some skill. Tools and libraries help here. On Android we get
NetworkOnMainThreadException when trying to do blocking IO in the UI thread, while for asynchronous backends there are tools like Reactive Audit that help you identify blocking calls in your code.
Shall we have await, too?
So, we mark asynchronous functions with suspend modifier in Kotlin and argue that it helps. Shouldn’t we apply the same argument to the call-sites of these functions and introduce some kind of await? Interestingly enough, if you read Async/await proposal for Swift, you find that its design is very similar to Kotlin Coroutines Design (it is direct, promise-free and uses continuations under the hood), but syntactically it follows C# tradition with async keyword to mark functions and await keyword on their call-sites, even though with promise-free design await can be made optional since compiler knows when asynchronous function is being invoked.
This highlights different philosophy behind Swift and Kotlin design, despite their otherwise similar looks. It shows elsewhere. Take a look at exceptions, for example. In Swift every exception-throwing statement has to be prefixed with try, even though it is not technically needed. Compiler can figure it out. Same with await. The proposal just follows Swift tradition.
Tradition is different in Kotlin. Idiomatic Kotlin code is focused on the business logic, the core problem at hand that programmer is solving at the moment. Asynchrony is a secondary concern. It should not stand in the way of understanding the business logic of the code, thus suspension points are highlighted by IDE in the gutter, they are not represented in the source code directly.
Frankly, the decision to go forward without any await keyword in the source was not an easy one. The Go language had helped us here, for sure. Go served as a proof that you can be successful without any language-level distinction between asynchronous and synchronous whatsoever.
Why is it called suspend after all?
Kotlin abolished await, but why would not we just mark asynchronous functions with async, just like most other languages with colored functions do? The reason boils down to both accident and semantics. Suspending functions in Kotlin can be synchronous. You can define a synchronous sequence using
sequence builder in Kotlin. It would be utterly wrong, semantically, to say that
yield is an asynchronous function. The control flow is fully synchronous when you yield the next sequence element. The term suspending function and the corresponding suspend modifier is the best generalization that we could find.