Kotlin - Как генерировать рекурсивные функции, которые не могут быть преобразованы? - PullRequest
0 голосов
/ 30 августа 2018

Я хочу обобщить следующую функцию:

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

Я не могу выполнить вышеизложенное, поскольку в рекурсивных функциях не разрешены типы reified.

Как я могу генерировать эту функцию в Kotlin?

Ответы [ 2 ]

0 голосов
/ 14 февраля 2019

Вы можете определить функцию внутри встроенной функции.

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

См. этот рабочий тест

Обратите внимание, что он не требует хвостовой рекурсии, то есть он будет работать даже с NP-сложными рекурсивными алгоритмами.

Почему это работает:

  • В каждом вызове execute64 создается новый анонимный класс.
  • Анонимный класс позволяет нам определять функцию внутри функции.
  • У нас есть фактическая скомпилированная функция (а не просто встроенная функция). Таким образом, мы можем вызывать эту функцию рекурсивно, не беспокоясь о inline, потому что область действия inline d находится вне области действия этой функции.

Примечание:

  • Это приведет к определению нового анонимного класса при каждом вызове execute64(). Таким образом, если в вашем коде 100 мест, которые вызывают эту функцию напрямую, будет создано 100 анонимных классов. Ваш код может страдать от медленной загрузки классов. (нужна цитата, я не специалист по загрузке классов)
  • Этот метод все еще включает рекурсивный вызов метода и создает один экземпляр каждый раз, когда вызывается встроенная функция execute64 (но новый экземпляр не создается во время рекурсии). Я не уверен, что эта встроенная функция все еще пользуется повышением производительности от встроенных лямбд, но она определенно решает проблему с reified.
0 голосов
/ 30 августа 2018

Вы можете сделать рекурсивную функцию не встроенной и взять 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 вместо того, чтобы сделать обобщенный параметр reified, что в основном и будет сделано в Java, если вам нужна проверка instanceof с универсальным параметром.

Однако, вы можете свернуть свой стек ( Путь от рекурсии к итерации ):

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

Я не проверял это, но общая идея должна работать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...