Kotlin-如何生成无法具体化的递归函数?

时间:2018-08-30 03:03:44

标签: generics recursion kotlin

我想泛化以下功能:

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中泛化该功能?

2 个答案:

答案 0 :(得分:4)

您可以使递归函数成为非内联函数,并使用表示所需类型的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))
                }
            }
        }
    }
}

我还没有测试过,但是总体思路应该可行。

答案 1 :(得分:0)

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

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硬递归算法也可以使用。

为什么起作用:

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

注意:

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