The Code A is from the question answered by Roman Y.
The Code A can work well when it invokes with background(appState)() {...}
, why can't I remove parentheses ()?
But Code B fails when it invoke with background(appState) {...}
, why?
And more Code C can work well when it invokes with val aa=background(appState) aa{...}
, why?
Code A
@Composable
fun NiaApp(
windowSizeClass: WindowSizeClass,
appState: NiaAppState = rememberNiaAppState(windowSizeClass)
) {
NiaTheme {
background(appState)() {
Scaffold(
...
) { padding ->
}
}
}
}
@Composable
fun background(appState: NiaAppState): @Composable (@Composable () -> Unit) -> Unit =
when (appState.currentDestination?.route) {
ForYouDestination.route -> { content ->
NiaGradientBackground(content = content) }
else -> { content -> NiaBackground(content = content) }
}
Code B
@Composable
fun NiaApp(
windowSizeClass: WindowSizeClass,
appState: NiaAppState = rememberNiaAppState(windowSizeClass)
) {
NiaTheme {
background(appState){
Scaffold(
...
) { padding ->
}
}
}
}
...
Code C
@Composable
fun NiaApp(
windowSizeClass: WindowSizeClass,
appState: NiaAppState = rememberNiaAppState(windowSizeClass)
) {
val aa=background(appState)
NiaTheme {
aa{
Scaffold(
...
) { padding ->
}
}
}
}
...
This is more of a Kotlin
function
question than a Compose
one, so we will omit all Jetpack Compose
related codes here, leaving only Kotlin
related context to keep everything in focus.
Lets first define key points
and mark them with a character.
Key point A
. Lambda invocaton : lambda .invoke
is equal to ()
Key point B
. Based on the DocumentationPassing trailing lambdas:
According to Kotlin convention, if the last parameter of a function is a function, then a lambda expression passed as the corresponding argument can be placed outside the parentheses:
...
If the lambda is the only argument in that call, the parentheses can be omitted entirely
…
We'll make the background function look like this without the @Composable
annotation and the NiaAppState
arg, leaving it with NO parameters, but we will keep the function calls the same just so we're on track. I also named the returning lambda's parameter for a clearer picture.
fun background() : (anotherLambda: () -> Unit) -> Unit { ... }
The Code A can work well when it invokes with background(appState)() {...}, why can't I remove parentheses ()?
But Code B fails when it invoke with background(appState) {...}, why?
Let's break down your CodeA
and CodeB
at the same time to answer your 2 questions above. But remember, we are using our own background
function and not the compose one.
1:
Start first by a simple invocation, here we are just calling the background function, and disregarding the value that it returns, nothing unusual here
background()
2:
While here, we are calling the background function, but also INVOKING the returned lambda (
Key point A
) immediately, we will get a compile error here 'No value passed for parameter anotherLambda' because when we INVOKE it, it requires US to PASS an argument to it which is a type of() -> Unit
background()() // <-- compile error: No value passed for parameter anotherLambda
3: The Code A can work well when it invokes with background(appState)() {...}
Here the compile error disappears when we specified the lambda block { ... } , because
- We invoked the returned lambda (
Key point A
) immediately- And we supplied it with a lambda argument, and since the code follows
Key point B
it worked by simply calling the argument lambda's block { ... }
background()() {
...
}
4: But Code B fails when it invoke with background(appState) {...}, why?. Why can't I remove parentheses ()?
Here, we'll get another kind of error, 'Too many arguments for public fun background() ...'.
Because we are NOT INVOKING the returned lambda, we are simply calling the background() function itself and it doesn't have any lambda parameters or any parameters at all, check the background function signature we did above and
Key point B
.And your actual background function has only one parameter (appState: NiaAppState) and it's not a lambda type parameter, again check
Key point B
.
background() { // big chunk of compile error here: Too many arguments for public fun background() ...
...
}
5:
This is the version (check #3) without
Key point B
. We invoke the returned lambda (Key point A
) immediately and pass a lambda argument inside of it.
background () ( {
...
} )
Equivalent background()
calls using the lambda's invoke()
instead of a parenthesis ()
:
// #2
background().invoke() // <-- compile error: No value passed for parameter anotherLambda
// #3
background().invoke {
...
}
// #4
background(invoke { // big chunk of compile error here: Too many arguments for public fun background() ... and Unresolved reference invoke
...
} )
// #5
background().invoke ( {
...
} )
And more Code C can work well when it invokes with val aa=background(appState) aa{...}, why?
Finally lets break down CodeC
:
1:
Here we call the background function, and because we have an assignment operation with the type inferred,
aa
is now the returned lambda value from background() invocation
val aa = background()
// your actual background function equivalent
val aa = background(appState)
2:
Assignment declaration with specified type.
// aa assignment declaration with type specified
val aa : (() -> Unit) -> Unit = background()
// your actual background function equivalent
val aa : @Composable (@Composable () -> Unit) -> Unit = background(appState)
3:
Assignment declaration with specified type and with a defined name for aa's lambda parameter.
// aa assignment declaration with type specified and named parameter
val aa : (aaLambdaParam : () -> Unit) -> Unit = background()
// your actual background function equivalent
val aa : @Composable (aaLambdaParam: @Composable () -> Unit) -> Unit = background(appState)
4:
aa
is the returned lambda that accepts an argument of type() -> Unit
and because ofKey point B
, we can omit the parenthesis and directly call the block {...} of the passing lambda argument
aa {
...
}
5:
But if we call it this way, we'll get an error, 'No value passed for parameter ...', because
aa
is now a function that expects an argument of type() -> Unit
, seeKey point A
.
aa() // <-- No value passed for parameter 'p1' (#2) or 'No value passed for parameter 'aaLambdaParam' (#3)
6:
This is the version (check #4) without
Key point B
. We invoke the lambda (Key point A
) and pass a lambda argument inside of it.
aa ( {
...
} )
Equivalent aa
calls using the lambda's invoke()
instead of a parenthesis ()
:
// #4, Key point B
aa.invoke {
...
}
// #5
aa.invoke() // <-- No value passed for parameter 'p1' or 'aaLambdaParam'
// #6, not Key point B
aa.invoke( {
...
} )
I would suggest re-visiting Kotlin's High-Order Functions and Lambdas.