我如何调用一个返回lambda的函数,该函数还接受另一个lambda作为它的参数(()->Unit)->Kotlin中的Unit?

k5hmc34c  于 2022-11-25  发布在  Kotlin
关注(0)|答案(2)|浏览(195)

代码A来自罗马人Y回答的question
代码A在调用background(appState)() {...}时可以很好地工作,为什么我不能去掉括号()?
但是代码B在调用background(appState) {...}时失败了,为什么?
当用val aa=background(appState) aa{...}调用时,更多的代码C可以很好地工作,为什么?

代码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) }
            }

代码B

@Composable
fun NiaApp(
        windowSizeClass: WindowSizeClass,
        appState: NiaAppState = rememberNiaAppState(windowSizeClass) 
) {
        NiaTheme {
            background(appState){
                Scaffold(
                    ...
                ) { padding ->
                  }
            }     
        }
}

...

代码C

@Composable
fun NiaApp(
        windowSizeClass: WindowSizeClass,
        appState: NiaAppState = rememberNiaAppState(windowSizeClass) 
) {
        val aa=background(appState)

        NiaTheme {
            aa{
                Scaffold(
                    ...
                ) { padding ->
                  }
            }     
        }
}

...
izkcnapc

izkcnapc1#

这更像是一个Kotlinfunction问题,而不是一个Compose问题,因此我们将在这里省略所有与Jetpack Compose相关的代码,只留下与Kotlin相关的上下文,以使所有内容都集中在焦点上。
让我们首先定义key points并使用字符标记它们。

  • Key point A. λ调用:λ .invoke等于()
  • Key point B。基于文档

传递尾随lambda:
根据Kotlin约定,如果函数的最后一个参数是函数,那么作为相应参数传递的lambda表达式可以放在括号之外:
...
如果lambda是该调用中的唯一参数,则可以完全省略括号
...
我们将使后台函数看起来像这样,没有@Composable注解和NiaAppState参数,只留下NO参数,但我们将保持函数调用相同,以便我们继续前进。为了更清楚起见,我还命名了返回的lambda参数。

fun background() : (anotherLambda: () -> Unit) -> Unit { ... }

代码A在调用background(appState)(){...}时可以很好地工作,为什么我不能去掉括号()呢?
但是代码B在使用background(appState){...}调用时失败,为什么?
让我们同时分解你的CodeACodeB来回答你上面的两个问题。但是记住,我们使用的是**我们自己的****background**函数,而不是compose函数。
一句话:
首先从一个简单的调用开始,这里我们只是调用后台函数,忽略它返回的值,这里没有什么异常

background()

二:
在这里,我们调用后台函数,但同时也INVOKING返回的lambda(Key point A立即,我们将在这里得到一个编译错误“No value passed for parameter anotherLambda”,因为当我们INVOKE它时,它要求我们向它传递一个类型为() -> Unit的参数

background()() // <-- compile error: No value passed for parameter anotherLambda

3:代码A在后台调用(appState)(){...}时可以正常工作
这里,当我们指定lambda块{...}时,编译错误消失了,因为

  • 我们立即调用返回的lambda(Key point A
  • 我们给它提供了一个lambda参数,因为代码遵循Key point B,所以只需调用参数lambda的block {...}
background()() {
   ...
}

4:但是代码B在后台调用(appState){...}时失败,为什么?为什么我不能删除括号()?
在这里,我们将得到另一种错误,'Too many arguments for public fun background()...'。
因为我们没有调用返回的lambda,我们只是调用了background()函数本身,它没有任何lambda参数或任何参数,请检查我们上面所做的后台函数签名和Key point B
实际的后台函数只有一个参数(appState:NiaAppState),而且它不是lambda类型参数,再次检查Key point B

background() {  // big chunk of compile error here: Too many arguments for public fun background() ...
   ...
}

五:
这是没有**Key point B的版本(检查#3)。我们立即调用返回的lambda(Key point A并在它内部**传递一个lambda参数。

background () ( {
   ...
} )

使用lambda的invoke()而不是括号()的等效background()调用:

// #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 ( {
    ...
} )

而更多的代码C在调用瓦尔aa=background(appState)aa{...}时可以很好地工作,为什么呢?
最后,让我们分解一下CodeC
一句话:
这里我们调用了background函数,因为我们有一个带有推断类型的赋值操作,aa现在是background()调用返回的lambda值

val aa = background()

// your actual background function equivalent
val aa = background(appState)

二:
具有指定类型的赋值声明。

// aa assignment declaration with type specified
val aa : (() -> Unit) -> Unit = background()

// your actual background function equivalent
val aa : @Composable (@Composable () -> Unit) -> Unit  = background(appState)

第三章:
具有指定类型和aa的lambda参数的已定义名称的赋值声明。

// 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)

第四章:
aa是返回的lambda,它接受() -> Unit类型的参数,由于Key point B,我们可以省略括号并直接调用传递lambda参数的块{...}

aa {
   ...
}

五:
但是如果我们这样调用它,我们会得到一个错误,'No value passed for parameter...',因为aa现在是一个需要() -> Unit类型参数的函数,请参见Key point A

aa() // <-- No value passed for parameter 'p1' (#2) or 'No value passed for parameter 'aaLambdaParam' (#3)

六:
这是没有**Key point B的版本(检查#4)**。我们调用lambda(Key point A)并在其中传递lambda参数。

aa ( {
   ...
} )

使用lambda的invoke()而不是括号()的等效aa调用:

// #4, Key point B
aa.invoke {
   ...
}

// #5
aa.invoke() // <-- No value passed for parameter 'p1' or 'aaLambdaParam'

// #6, not Key point B
aa.invoke( {
   ...
} )

我建议再来这里。

m0rkklqb

m0rkklqb2#

这是一个高阶函数的问题,这个例子会比较容易理解

fun getResult(): (innerLambda: () -> Int) -> Unit = { innerLambda ->
    println(innerLambda())
    println("result")
}

innerLambda将返回一个数字,因此当我们调用函数getResult时

getResult() // this is type (innerLambda: () -> Int) -> Unit

第二个括号是invoke函数,因此

getResult().invoke()
getResult()()

这两条线是一样的,当我们到达这里时,我们必须传入innerLambda,因此它看起来像

getResult()({ 100 })

此时ide会提示您Lambda argument should be moved out of parentheses,如下所示

getResult()(){ 100 }

版本A结果
而对于C版本则比较简单,用一个变量来curried一个函数是函数编程中常见的,调用curried函数,所以当我们弄清楚A版本的时候,我们可以查出来下面的代码

val getApiResult:(innerLambda: () -> Int) -> Unit = getResult()
getApiResult.invoke({ 100 })

步骤与上一个示例相同,将lambda移出

getApiResult.invoke(){ 100 }

当lambda是最后一个参数时,我们可以删除不必要括号

getApiResult.invoke { 100 }
getApiResult() { 100 }

这两行是一样,请记住,在Kotlin中,当lambda是最后一个参数时,我们可以删除不必要括号,再次,最终我们得到了

getApiResult { 100 }

添加与上述答案相关的一些示例

class InnerLambda:Function0<Int> {
    override operator fun invoke(): Int {
        return 100
    }
}

fun clickListener(event:() -> Unit) {

}
clickListener {

}

运算符fun是常用,如+unaryPlus,这些都是Kotlin语法的糖,所以在Kotlin中3.unaryPlus(5)等于3 + 5,这就是为什么invoke可以用()代替

相关问题