【问题标题】:Kotlin: How to explicitly specify the actual type of a lambda?Kotlin:如何明确指定 lambda 的实际类型?
【发布时间】:2021-10-25 09:36:48
【问题描述】:

如何在 Kotlin 中明确指定 lambda 的实际类型?

这是必要的,因为如果我有两个采用相似 lambdas 的函数,例如

fun use(block: Context.() -> Boolean)
@JvmName("use2") fun use(block: Context.() -> Unit)

我希望能够让use2 调用常规(布尔)use,即

@JvmName("use2") fun use(block: Context.() -> Unit) = use { block(); true }

这类似于您可以显式指定 SAM 类型,例如:

Runnable { println("hello") }

如何对 Kotlin lambda 类型执行相同操作?

这是我想出的解决问题的方法,但我想知道是否有更好/更清洁的方法。

@JvmName("use2") fun use(block: Context.() -> Unit) = use({ block(); true } as (Context.() -> Boolean)})

【问题讨论】:

  • 我怀疑共识将是首先允许这种混淆 - 即不要有两个具有相同名称但仅在 lambda 类型不同的函数(和,尤其是仅在 lambda 的 return 类型中)。任何手动指定类型的方式都将是丑陋和尴尬的,并且阅读起来很混乱。然而,如果你能在函数 name 中明确区别,你就会避免所有这些麻烦!

标签: java function kotlin lambda types


【解决方案1】:

我已经让它工作了:

@JvmName("use2") fun use(block: Context.() -> Unit) =
    use({ foo: Context -> block(foo); true } as (Context) -> Boolean)

删除演员表或: String 类型注释将使其无法编译。 IntelliJ IDEA 还错误地报告强制转换是不必要的 (note that this isn't the first time IntelliJ is wrong)

请注意,我已将 lambda 的类型从 Context.() -> Boolean 更改为 (Context) -> Boolean,因为我认为没有办法明确指定 receiver 类型。不过,这不会影响重载决议。

如果你真的想使用接收器类型为Context 的东西,你可以改用匿名函数:

@JvmName("use2") fun use(block: Context.() -> Unit) =
    use(fun Context.(): Boolean { block(); return true })

我认为这是编写 use2 的更好方式,但请注意匿名函数与 lambda in many ways 不同。

【讨论】:

    【解决方案2】:

    你可以使用@OverloadResolutionByLambdaReturnType注解,但这是一个实验特性,所以你需要添加@OptIn(ExperimentalTypeInference::class)。您还应该明确指定返回类型。

    fun use(block: Context.() -> Boolean): ReturnType
    
    @OptIn(ExperimentalTypeInference::class)
    @OverloadResolutionByLambdaReturnType
    @JvmName("use2")
    fun use(block: Context.() -> Unit): ReturnType = use { block(); true }
    

    【讨论】:

      【解决方案3】:

      如何在 Kotlin 中明确指定 lambda 的实际类型?

      我认为有两个选项可以使代码更加清晰:泛型和函数式接口。它们也可以组合!

      选项 1:泛型(* 请参阅下面的更新)

      不要指定返回值 - 改用泛型。我们可以只为所有交互使用一个函数:

      fun <R> use(block: Context.() -> R) : Boolean = context.block()
      

      如果block 不返回布尔值,您还希望返回默认值true。我们也可以这样做:

      fun <R> use(block: Context.() -> R): Boolean {
        // use a 'when' block - it solves type safety issues
        return when (
          val result = context.block() // execute the lambda and store the output in a val
        ) { 
          !is Boolean -> true   // default to 'true' if result is not a boolean
          else        -> result // else, we can return the boolean result of 'block'
        }
      }
      

      *更新实际上,泛型不是必需的,因为您的 use(...) 函数总是返回布尔值,而不是 lambda 的结果。

      这里有一个非泛型 use(...) 函数,可以解决“两个函数的 jvm 签名冲突”的问题:

      fun  use(block: Context.() -> Any): Boolean {
        // use a 'when' block - it solves type safety issues
        return when (
          val result = context.block() // execute the lambda and store the output in a val
        ) {
          !is Boolean -> true   // default to 'true' if result is not a boolean
          else        -> result // else, we can return the boolean result of 'block'
        }
      }
      

      选项 2:功能接口

      使用functional interfaces - 它使 lambda 的定义更加清晰和可重用。

      // step 1: define our 'lambdas'
      
      // equivalent: Context.() -> Boolean
      fun interface ContextPredicate {
        fun Context.execute(): Boolean
      }
      
      // equivalent: Context.() -> Unit
      fun interface ContextConsumer {
        fun Context.execute()
      }
      
      // step 2: define the 'use' functions
      class ContextService(
        private val context: Context
      ) {
      
        fun use(block: ContextPredicate): Boolean = with(block) {
          context.execute()
        }
      
        fun use(block: ContextConsumer): Boolean = with(block) {
          context.execute()
          true // default response is 'true' for 'ContextConsumer'
        }
      }
      
      // 3. dummy class for testing
      data class Context(
        val name: String
      )
      
      // 4. test our code
      fun main() {
        val service = ContextService(Context("hello"))
      
        println(
          service.use(ContextPredicate { name == "hello" })
        )
        // output: true
      
        println(
          service.use(ContextPredicate { name == "good morning" })
        )
        // output: false
      
        println(
          service.use(ContextConsumer { println("~~~$name~~~") })
        )
        // output:
        // ~~~hello~~~
        // true
      }
      

      现在返回类型没有歧义了 - use(...) 函数要么接受谓词,要么接受消费者,故事结束。

      注意:with(block) { ... } (read more about it here) 是必需的,因为我们定义的功能接口具有接收器。

      泛型 + 函数式接口

      我们可以将两者结合起来,以获得两全其美:简洁明了:

      // define a generic fun-interface
      fun interface ContextFunction<R> {
        // change Context to be a parameter, not a receiver 
        fun execute(context: Context): R
      }
      
      class ContextService(
        private val context: Context
      ) {
        
        // use our generic fun-interface
        fun <R> use(block: ContextFunction<R>): Boolean {
          
          return when (val result = block.execute(context)) {
            // if the result isn't a boolean, then the default response is 'true'
            !is Boolean -> true
            // else, we can use the actual result
            else        -> result
          }
        }
      }
      
      // usage is the same as 'Option 1: Generics'
      fun main() {
        val service = ContextService(Context("hello"))
      
        println(
          service.use { it.name == "hello" }
        )
        // output: true
      
        println(
          service.use { it.name == "good morning" }
        )
        // output: false
      
        println(
          service.use { println("~~~${it.name}~~~") }
        )
        // output:
        // ~~~hello~~~
        // true
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-22
        • 2021-01-10
        • 2020-12-10
        相关资源
        最近更新 更多