Composable functions have different limitations and capabilities than standard functions. They have a different type, and model a very specific concern. This differentiation can be understood as a form of “function coloring”, since somehow they represent a separate category of functions.
Function coloring
“Function coloring” is a concept explained by Bob Nystrom from the Dart team at Google in a blockpost called “What color is your function?”, written in 2015. He explained how async and sync functions don’t compose well together, since you cannot call async functions from sync ones, unless you make the latter also async, or provide an awaiting mechanism that allows to call async functions and await for their result. This is why Promises and async/await
were introduced by some libraries and languages. It was an attempt to bring composability back. Bob refers to these two function categories as two different “function colors”.
In Kotlin, suspend
aims to solve the same problem. However, suspend
functions are also colored, since we can only call suspend
functions from other suspend
functions. Composing programs with a mix of standard and suspend
functions requires some ad-hoc integration mechanism (coroutine launch points). The integration is not transparent to the developer.
Overall, this limitation is expected. We are modeling two categories of functions that represent concepts of a very different nature. It’s like speaking two different languages. We have operations that are meant to calculate an immediate result (sync), and operations that unfold over time and eventually provide a result (async), which will likely take longer to complete.
Coloring in Compose
In Jetpack Compose, the case of Composable functions is equivalent. We cannot call Composable functions from standard functions transparently. If we want to do that, an integration point is required (e.g: Composition.setContent
). Composable functions have a completely different goal than standard functions. They are not designed to write program logics, but to describe changes for a node tree.
It might seem that I am tricking a bit here. One of the benefits of Composable functions is that you can declare UI using logics, actually. That means sometimes we need to call Composable functions from standard functions. For example:
@Composable
fun SpeakerList(speakers: List<Speaker>) {
Column {
speakers.forEach {
Speaker(it)
}
}
}
The Speaker
Composable is called from the forEach
lambda, and the compiler does not seem to complain. How is it possible to mix function colors this way then?
Inline
The reason is inline
. Collection operators are declared as inline
, so they inline their lambdas into their callers making it effectively as if there was no extra indirection. In the above example, the Speaker
Composable call is inlined within the SpeakerList
body, and that is allowed since both are Composable functions. By leveraging inline
we can bypass the problem of function coloring to write the logic of our Composables. Our tree will be comprised of Composable functions only.
But, is coloring really a problem?
Well, it might be if we needed to combine both types of functions and jump from one to the other all the time. However, that is not the case either for suspend
or @Composable
. Both mechanisms require an integration point, and therefore we gain a completely colored call stack beyond that point (everything suspend, or Composable). This is actually an advantage, since it allows the compiler and runtime to treat “colored” functions differently, and enable more advanced language features that were not possible with standard functions.
Language features
In Kotlin, suspend
allows to model async non-blocking programs in a very idiomatic and expressive manner. The language gains the ability to represent a very complex concept in an extremely simple way: adding a suspend modifier to our functions. On the other hand, @Composable
makes standard functions become restartable, skippable, and reactive, which are capabilities that standard Kotlin functions do not have.
Learn more 📖
This post is part of the first chapter of the Jetpack Compose internals book. If you want to learn more about Composable functions, the Compose compiler, runtime, and Compose UI, that might be a purchase you’d want to consider.
👨🏫 Online training
You might want to consider attending the next edition of the highly exclusive “Jetpack Compose and internals” online training I’m giving in February. I have carefully crafted it so attendees can master the library from the Android development perspective, while learning about its internals in order to grow a correct and accurate mental model. I’ll be your teacher 🙌
This course will allow you to position yourself well in the Android development industry. Earlybird seats still available.