Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin - How to generify recursive functions that can't be reified?

I want to generify the following function:

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

So instead of hardcoding RadioButton, I would like to use a generic T, like this:

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

I can't do the above because reified types are not allowed in recursive functions.

How can I generify that function in Kotlin?

like image 474
Bitcoin Cash - ADA enthusiast Avatar asked Jan 27 '23 12:01

Bitcoin Cash - ADA enthusiast


1 Answers

You can make the recursive function non-inline and take a KClass representing the desired type, and make an additional wrapper function:

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)

You can't inline a recursive function, unless you can unroll it into a loop, because inlining a function means that after compilation it isn't a function anymore - it is instead copied directly into the call site. No function, no call stack, no recursion. In these cases, you have to pass a KClass instead of making the generic parameter reified, which is basically exactly what you'd do in Java if you needed an instanceof check with a generic parameter.

However, you can roll your own stack (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))
                }
            }
        }
    }
}

I haven't tested this, but the general idea should work.

like image 74
Salem Avatar answered Jan 31 '23 10:01

Salem