Функция расширения и оптимизация вызовов (TCO) в Котлине - PullRequest
0 голосов
/ 02 марта 2019

У меня есть следующая функция, использующая TCO:

tailrec fun superDigit(n: String): Int {
    val sum = n.fold(0) { sum, char -> sum + char.toString().toInt() }
    return if (sum < 10) sum else superDigit(sum.toString())
}

Если я реализую ту же функцию, что и функция расширения, например:

fun String.superDigit(): Int {
    val sum = fold(0) { sum, char -> sum + char.toString().toInt() }
    return if (sum < 10) sum else sum.toString().superDigit()
}

Оптимизирован ли хвостовой вызов функции расширения?а также?

ИМО, вызывающая функцию расширения, все еще является обычным вызовом функции с this в качестве параметра, поэтому это все еще рекурсивный вызов, и поскольку tailrec не может использоваться в функциях расширения, я предполагаю, что это не так.был оптимизирован компилятором.Это предположение верно или нет?

1 Ответ

0 голосов
/ 03 марта 2019

, поскольку tailrec нельзя использовать в функциях расширения

Вы уверены?

Я только что проверил это, посмотрев байт-код Kotlin в IntelliJIDEA.Прежде всего, код с tailrec в функции расширения успешно компилируется.Пойдем дальше: сравните две части кода Kotlin и байт-кода ниже, один с tailrec, а другой без.

Kotlin:

fun Double.tailrecTestExtension(): Double
        = (this - 1.0).tailrecTestExtension()

Байт-код:

  // access flags 0x19
  public final static tailrecTestExtension(D)D
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
   L0
    LINENUMBER 13 L0
    DLOAD 0
    DCONST_1
    DSUB
    INVOKESTATIC com/example/TestKt.tailrecTestExtension (D)D
    DRETURN
   L1
    LOCALVARIABLE $receiver D L0 L1 0
    MAXSTACK = 4
    MAXLOCALS = 2

Kotlin:

tailrec fun Double.tailrecTestExtension(): Double
        = (this - 1.0).tailrecTestExtension()

Байт-код:

  // access flags 0x19
  public final static tailrecTestExtension(D)D
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
   L0
    LINENUMBER 13 L0
    DLOAD 0
    DCONST_1
    DSUB
    DSTORE 0
    GOTO L0
   L1
    LOCALVARIABLE $receiver D L0 L1 0
    MAXSTACK = 4
    MAXLOCALS = 2

Обратите внимание, что в первом примере есть вызов INVOKESTATIC (что соответствует обычной рекурсии), который был заменен нарегулярный переход (GOTO) во второй версии (что соответствует циклически ожидаемому поведению, введенному tailrec).

Примечание: я не эксперт по байт-коду Kotlin, мое понимание основанона некоторые базовые знания о языке ассемблера.Здесь я предполагаю, что это знание может быть передано в байт-код Котлина.

...