【问题标题】:Kotlin - How to generify recursive functions that can't be reified?Kotlin - 如何生成无法具体化的递归函数?
【发布时间】:2018-08-30 03:03:43
【问题描述】:

我想生成如下函数:

fun ViewGroup.allRadioButtons(f: (RadioButton) -> Unit){
    this.afterMeasured {
        for(i in 0 until childCount){
            val child = getChildAt(i)
            if(child is RadioButton){
                f(child)
            }
            if(child is ViewGroup){
                child.allRadioButtons(f)
            }
        }
    }
}

所以我不想硬编码RadioButton,而是使用通用的T,如下所示:

inline fun <reified T> ViewGroup.allViewsOfTypeT(f: (T) -> Unit){
    this.afterMeasured {
        for(i in 0 until childCount){
            val child = getChildAt(i)
            if(child is T){
                f(child)
            }
            if(child is ViewGroup){
                child.allRadioButtons(f)
            }
        }
    }
}

我不能这样做,因为递归函数中不允许具体类型。

如何在 Kotlin 中生成该函数?

【问题讨论】:

    标签: generics recursion kotlin


    【解决方案1】:

    您可以将递归函数设为非内联函数并采用代表所需类型的KClass,并制作一个额外的包装函数:

    fun <T : View> ViewGroup.allViewsOfTypeT(type: KClass<T>, f: (T) -> Unit) {
        afterMeasured {
            for (i in 0 until childCount) {
                val child = getChildAt(i)
                if (type.isInstance(child)) f(child)
                if (child is ViewGroup) child.allViewsOfTypeT(type, f)
            }
        }
    }
    
    inline fun <reified T : View> ViewGroup.allViewsOfTypeT(f: (T) -> Unit)
        = allViewsOfTypeT(T::class, f)
    

    你不能内联递归函数,除非你可以将它展开到一个循环中,因为内联函数意味着编译后它不再是一个函数——而是直接复制到呼叫站点。没有函数,没有调用堆栈,没有递归。在这些情况下,您必须传递 KClass 而不是具体化泛型参数,如果您需要使用泛型参数检查 instanceof,这基本上正是您在 Java 中所做的。

    但是,您可以滚动自己的堆栈 (Way to go from recursion to iteration):

    inline fun <reified T : View> ViewGroup.allViewsOfTypeT(action: (T) -> Unit) {
        val views = Stack<View>()
    
        afterMeasured {
            views.addAll((0 until childCount).map(this::getChildAt))
        }
    
        while (!views.isEmpty()) {
            views.pop().let {
                if (it is T) action(it)
                if (it is ViewGroup) {
                    afterMeasured {
                        views.addAll((0 until childCount).map(this::getChildAt))
                    }
                }
            }
        }
    }
    

    我没有对此进行测试,但总体思路应该可行。

    【讨论】:

    • 嗨,它尝试了实现这个,addViews 方法吐出一个错误,上面写着local functions are not yet supported in inline functions。有什么解决方法吗?
    • @DaleJulian 哎呀。我使用函数的唯一原因是为了使内容更具可读性,您可以将函数调用替换为其内容(或将其提取并使其成为private
    • 谢谢!但为了澄清,您在此处使用的 afterMeasured 方法与此处找到的方法相同:antonioleiva.com/kotlin-ongloballayoutlistener ?
    • @DaleJulian 我不确定,我只是使用该功能,因为 OP 使用了它(afterMeasured {} 部分 功能的重复部分,您可以替换它与任何东西)。它与递归 inline 函数没有任何关系。
    【解决方案2】:

    您可以在内联函数中定义一个函数。

    inline fun <reified T> execute64(crossinline run: (Class<*>, Int) -> Unit) {
        var cnt = 0
        val x = object : Consumer<Int> {
            override fun accept(i: Int) {
                if(i > 1) {
                    repeat(2) { accept(i shr 1) }
                    return
                }
                run(T::class.java, cnt++)
            }
        }
        x.accept(64)
    }
    

    this working test

    请注意,它不需要尾递归,即它甚至可以使用 NP-hard 递归算法。

    为什么会这样:

    • 在每次调用 execute64 时,都会内联创建一个新的匿名类。
    • 匿名类允许我们在函数内部定义函数。
    • 我们有一个实际的编译函数(不仅仅是一个内联函数)。所以我们可以递归调用这个函数而不用担心内联,因为inlined的作用域在这个函数的作用域之外。

    注意:

    • 这将导致每次调用execute64() 时定义一个新的匿名类。因此,如果您的代码中有 100 个位置直接调用此函数,则会创建 100 个匿名类。您的代码可能会遇到类加载缓慢的问题。 (需要引用,我不是类加载专家)
    • 此方法仍然涉及递归调用方法,并且每次调用内联函数execute64 时都会实例化一个实例(但在递归过程中不会创建新实例)。我不确定这个内联函数是否仍能享受内联 lambda 带来的性能提升,但它确实解决了 reified 的问题。

    【讨论】:

      猜你喜欢
      • 2020-04-04
      • 2015-05-14
      • 2022-11-03
      • 1970-01-01
      • 2016-06-13
      • 1970-01-01
      • 2012-01-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多