簡體   English   中英

我如何調用返回 lambda 的 function,它還接受另一個 lambda 作為其參數 ( () -> Unit ) -> Unit in Kotlin?

[英]How can I invoke a function that returns a lambda which also accepts another lambda as its parameter ( () -> Unit ) -> Unit in Kotlin?

代碼 A 來自 Roman Y 回答的問題

代碼A在調用background(appState)() {...}時可以正常工作,為什么我不能去掉括號()?

但是代碼 B 在使用background(appState) {...}調用時失敗了,為什么?

還有更多代碼 C 在調用時可以正常運行val aa=background(appState) aa{...} ,為什么?

代碼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 ->
                  }
            }     
        }
}

...

這與其說是一個Compose問題,不如說是一個Kotlin function問題,因此我們將在這里省略所有與Jetpack Compose相關的代碼,只留下與Kotlin相關的上下文以使一切都成為焦點。

讓我們首先定義key points並用字符標記它們。

  • Key point A Lambda 調用:lambda .invoke等於()
  • Key point B . 基於文檔

傳遞尾隨 lambda:

根據 Kotlin 約定,如果 function 的最后一個參數是 function,那么作為相應參數傳遞的 lambda 表達式可以放在括號外:

...

如果 lambda 是該調用中的唯一參數,則可以完全省略括號

……


我們將使背景 function 在沒有@Composable注釋和NiaAppState arg 的情況下看起來像這樣,不帶任何參數,但我們將保持 function 調用相同,以便我們走上正軌。 我還命名了返回的 lambda 參數以獲得更清晰的圖片。

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

代碼A用background(appState)(){...}調用時可以正常運行,為什么我不能去掉括號()?

但是代碼 B 在使用 background(appState) {...} 調用時失敗了,為什么?


讓我們同時分解您的CodeACodeB來回答您上面的 2 個問題。 但請記住,我們使用的是我們自己的background function 而不是 compose 背景。

1:

先從一個簡單的調用開始,這里我們只是調用后台function,忽略它返回的值,沒什么異常

background()

2:

在這里,我們調用后台 function,同時調用返回的lambdaKey point A ),這里會出現編譯錯誤 ' No value passed for parameter anotherLambda ' 因為當我們調用它時,它需要我們通過一個它的參數是() -> Unit的一種類型

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

3:代碼 A 在使用 background(appState)() {...} 調用時可以很好地工作

當我們指定 lambda 塊 {...} 時,這里編譯錯誤消失了,因為

  • 我們立即調用了返回的 lambda( Key point A
  • 我們為它提供了一個 lambda 參數,並且由於代碼遵循Key point B ,它通過簡單地調用參數 lambda 塊 {... }
background()() {
   ...
}

4:但是代碼 B 在使用 background(appState) {...} 調用時失敗了,為什么? 為什么我不能刪除括號 ()?

在這里,我們會得到另一種錯誤,“ Too many arguments for public fun background()... ”。

因為我們沒有調用返回的 lambda,所以我們只是調用 background() function 本身,它沒有任何 lambda 參數或根本沒有任何參數,請檢查我們在上面所做的背景 function 簽名和Key point B

而你的實際后台 function 只有一個參數 (appState: NiaAppState) 而不是 lambda 類型的參數,再次檢查Key point B

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

5:

這是沒有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 在用 val aa=background(appState) aa{...} 調用時可以正常工作,為什么?

最后讓我們分解CodeC

1:

這里我們調用后台 function,因為我們有一個類型推斷的賦值操作, aa現在是從 background() 調用返回的 lambda 值

val aa = background()

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

2:

指定類型的賦值聲明。

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

具有指定類型和 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)

4:

aa是返回的 lambda ,它接受() -> Unit類型的參數,由於Key point B ,我們可以省略括號並直接調用傳遞的塊 {...} lambda 參數

aa {
   ...
}

5:

但是如果我們這樣調用它,我們會得到一個錯誤,'No value passed for parameter...',因為aa現在是一個 function ,它需要一個() -> Unit類型的參數,參見Key point A

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

6:

這是沒有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( {
   ...
} )

我建議重新訪問Kotlin 的高階函數和 Lambdas

這是一道高階題function,這個例子會更容易理解

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

innerLambda 將返回一個數字,所以當我們調用 function getResult 時

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

第二個括號是invoke function,所以

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

這兩行是一樣的,當我們到達這里時,我們必須將innerLambda ,所以它看起來像

getResult()({ 100 })

此時ide會提示你Lambda argument should be moved out of parentheses ,像這樣

getResult()(){ 100 }

結果版本

而對於版本C就簡單了,在函數式編程中用一個變量柯里化一個function很常見,調用柯里化function,所以當我們弄清楚版本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 語法糖,所以3.unaryPlus(5)等於 Kotlin 中的3 + 5 ,這就是為什么invoke可以替換為()

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM