I have run into a limitation of the Kotlin languag...
# compose-web
e
I have run into a limitation of the Kotlin language (not sure if really the case, but I feel limited) that others likely will run into as well. I've defined a composable like so
Copy code
@Composable
fun MyComposable(vararg composables: @Composable () -> Unit) { /* impl */ }
Now I want to let the caller pass some styling also, so I refactor it to:
Copy code
@Composable
fun MyComposable(
    attrs: AttrBuilderContext<HTMLDivElement>? = null,
    vararg composables: @Composable () -> Unit
) { /* impl using a div that applies attrs */
}
Now all my code no longer compiles, because wherever I used to call e.g.
Copy code
MyComposable(
    { /* Some composable */ },
    { /* Some composable */ },
)
now suddenly the first lambda is used as the
attrs
parameter! It doesn't compile, luckily, because it's not allowed to call a
@Composable
function from a non-composable context: the
AttrBuilderContext
happens to be a lambda that
@Composable () -> Unit
fits into... 🤦‍♂️ A coincidence, but one that will likely happen to more users. The workaround beats the purpose of using a lambda in the first place:
Copy code
MyComposable(
    composables = arrayOf(
        { /* Some composable */ },
        { /* Some composable */ },
    )
)
This works, and is not nice. See also this thread.
I'm not sure if anything can be done here to improve the user experience. In the end, with the current state of the language, it's an error to think that
Copy code
@Composable
fun MyComposable(
    attrs: AttrBuilderContext<HTMLDivElement>? = null,
    vararg composables: @Composable () -> Unit
) { /* impl using a div that applies attrs */
}

// Somewhere in code
MyComposable(
    { /* Some composable */ },
    { /* Some composable */ },
)
Though, maybe the CSS API in compose-web can be improved. This is not an issue in compose for Android, because styling happens with
Modifier
objects as the first parameter (with usually a default), so signature clashes likely won't happen there!
n
Most of the time when you think you've run into a limitation with a major language it's because you're working against the grain, not because the language doesn't support your needs. In this case, your use case is easily solved with tools that are already in the language. Either pass
null
in explicitly or, if you don't want to break existing call sites, make an overload that has the same signature as the original function:
Copy code
@Composable
 fun MyComposable(
     vararg composables: @Composable () -> Unit
 ) =
    MyComposable(null, *composables)
This strategy for refactoring is one you want to hold on to for any language: if you need to refactor a function and don't want to break existing call sites, keep a version of the function that has the original signature. This is widely applicable in many situations, not just with varargs and not just in Kotlin. As for why the idiom you wanted to use isn't supported: when you're passing in arguments positionally, you can only omit arguments at the end, never in the beginning or in the middle, with the only exception being the single trailing lambda (which is an idiom that you are not using in your examples). Making another exception here for varargs would make the language's behavior much harder to reason about and not enable any patterns that aren't easily achievable already. (This is in contrast to the trailing lambda exception, which forms the basis for Kotlin's entire standard library.)
e
Thanks, very clear
Introducing a new refactored function signature while keeping the old one is nice, though in this case it doesn't work because of overload resolution ambiguity. Therefore I've opened an issue, not for the language, but for my specific use case: the compose-jb CSS API exposes core behaviour through a typealias that is actually just a lambda with a receiver, that matches against any lambda literal with or without that receiver. That is simply a feature of the Kotlin language, but the issue is that using this typealias in a lambda-heavy framework (compose) for a public API leads to situations like these. While the fix might be easy (e.g. introduce a stronger type than a type alias), the consequences might not be worth the change. Let's do further discussion in the issue, if it's of any value to anyone.