Is there any design document, e.g. in the KEEP-sty...
# compose
f
Is there any design document, e.g. in the KEEP-style, for the transformations performed by the composer compiler on a
@Composable
function?
z
s
You can read a kdoc on the ComposableFunctionBodyTransformer
c
There is no single document internally or externally that specifies what the plugin does. As @shikasd implies, the current source of truth is the compiler sources. For now the sources and talks like Jossi and Amber's talk are your best place for information about what the compiler does. We have accumulated design documents internally but they contain content that are not prepared for external presentation as they often contain references and links to documents that are not externally accessible and are not ready to be, or cannot be made to be, public. However, we have begun accumulating design documents in https://github.com/JetBrains/kotlin/tree/master/plugins/compose/design which is where such documents are likely to go and I have been opportunitstically converting our internal documents into documents we can share in the design directory. Is there something specific you would like covered?
f
Thanks for all the responses. I'm trying to understand the guarantees that we have (or don't have) on a composable function. Namely, how much of the Kotlin semantics is changed. For instance, https://developer.android.com/develop/ui/compose/mental-model#recomposition states
If you look at the code for a composable function, you might assume that the code is run in the order it appears. But this isn't necessarily true.
This sentence seems a bit scary, if the scope of the behaviour changes is not more well defined. Note that it refers to the code for a composable function and not the order on how those functions are called.
c
I believe this document overstates the changes we make. Ignoring lambdas for a bit,
@Composable
functions are normal function except that they 1) can skip if their parameters are the same as before (e.g. a memoized result), 2) can be called again directly if they read a mutable state that is changed (even if their parameters have not), and carry a silent parameter, the composer, which records the implied effect of composition. During initial composition (when it is creating the content, not updating it) the code executes without any reordering. When we recompose (to run the code to update the content) we ensure that all calls happen in the order they would have had the caller called them directly. That is,
StartScreen()
is always called before
MiddleScreen()
if both are called in the same composition. However,
StartScreen()
is not guaranteed to be called if
MiddleScreen()
is called as it may skip or the caller may have skipped, etc. Also, we guarantee that callers are executed before the code it calls. That is if
MyFancyNavigation
need to execute in the same composition as
StartScreen
it will execute before
StartScreen
(and the other calls made in the lambda). These guarantees are for correctness. That is if we didn't execute
MyFancyNavigation
first then we would not be sure the it even calls
StartScreen
for example, as it may not call the lambda in some cases. By correctness I mean that (ignoring mutable side effect outside the snapshot system) it would produce the same composition had all the functions been called without skipping or reordering. Composable lambdas, however, are called when the caller calls them. No surprise there. But they may be called outside of normal composition. This happens most often with what is referred as sub-composition as with
BoxWithConstraints()
and
LazyColumn()
. Any lambda may change in what phase of composition it is called. Mostly they are called synchronously but they may not be. This is no different from any other lambda in Kotlin but I call it out as it seems to contradict the paragraph above. Also, with the DSL syntax, lambdas can often be hidden so it often requires knowledge of what the composable function does to understand when something will execute. This is also like the rest of Kotlin but we lean on it a bit more heavily than other libraries. That said, it is best not to rely on these ordering guarantees. As the document implies, you should write in a style that is resilient to reordering of the execution and not rely on side-effects that would change if the order of execution changed. It is best to think of
@Composable
functions as pure functions, in the monadic sense, where the only side effects they have are recorded by the composer. An
@Composable
function should only produce the content it declares without any other effect. The order in which compose executes functions is deterministic and predictable, in a theoretical sense, but that doesn't mean, in practice, this is particularly easy to do or should be relied on.
f
Thanks for the very detailed response.