Можно ли создать рекурсивный тип функции в Котлине?

У меня есть функции, которые представляют шаги в процессе. Каждая функция также знает следующий шаг, если он есть. Я хотел бы иметь возможность сделать что-то вроде:

fun fooStep() : Step? {
    ... do something ...
    return ::barStep // the next step is barStep
}

Эти функции вызываются из центральной диспетчерской функции, которая содержит примерно такой код:

var step = startStep
while (step != null) {
    step = step()
}

Обратите внимание, что логика конкретного шага также определяет следующий шаг, если он вообще есть.

Я думал, что могу определить Step как:

typealias Step = () -> Step?

Таким образом, Step — это функция, которая возвращает еще один Step или ноль. Однако это не скомпилируется с:

Kotlin: Recursive type alias in expansion: Step

Я могу обойти это, обернув функцию в объект. например:

data class StepWrapper(val step: () -> StepWrapper?)

и соответствующим образом изменив сигнатуры моих функций.

К сожалению, это означает, что я не могу просто использовать функциональные литералы (например: ::barStep), но вместо этого должен обернуть их в StepWrapper:

fun fooStep() : StepWrapper? {
    ... do something ...
    return StepWrapper(::barStep)
}

(Я также должен изменить свой цикл отправки соответственно.)

Я хотел бы избежать необходимости создавать эти объекты-оболочки, если это возможно. Есть ли способ сделать это в Котлине?


person Laurence Gonsalves    schedule 09.06.2017    source источник
comment
github.com/Kotlin/KEEP/blob/master/proposals/ type-aliases.md Не в соответствии с этим, по крайней мере, не используя typealias   -  person Novaterata    schedule 10.06.2017
comment
Есть ли веская причина, по которой функция Step должна знать следующий шаг? Другим подходом может быть функция Step, которая возвращает условие в сочетании с графиком ссылок на функцию Step. Каждый узел в графе содержит вызываемую функцию шага, а затем Map‹Condition, Step› того, что вызывать дальше, в зависимости от возвращаемого условия. Тогда появится исполнитель, который сможет пройти по этому графу.   -  person bartonstanley    schedule 10.06.2017
comment
@roobyroo В чем преимущество подхода Map<Condition, Step> по сравнению с подходом StepWrapper, описанным в вопросе?   -  person Laurence Gonsalves    schedule 10.06.2017
comment
@Novaterata Да, я не ожидал, что это будет возможно с typealias, учитывая, что он явно указывает на проблему рекурсивных типов.   -  person Laurence Gonsalves    schedule 10.06.2017
comment
@LaurenceGonsalves Вот упрощенный пример, когда каждая функция имеет только один следующий шаг: data class Node(val step: () -> Unit, val nextNode: Node?) fun step1(): Unit { println("step 1") } fun step2(): Unit { println("step 2") } val node2 : Node = Node(::step2, null) val node1 : Node = Node(::step1, node2) var curNode: Node? = node1 while (curNode != null) { curNode.step.invoke() curNode = curNode.nextNode } Если функция может условно вернуть более одного шага, то необходима карта.   -  person bartonstanley    schedule 12.06.2017


Ответы (3)


Вы можете определить его, используя некоторый общий интерфейс:

interface StepW<out T> : ()->T?

interface Step : StepW<Step>


class Step1 : Step {
    override fun invoke(): Step? = Step2()
}

class Step2 : Step {
    override fun invoke(): Step? = null
}

Где Step — ваш тип рекурсивной функции.

person Logain    schedule 09.06.2017
comment
Интересно! Я не знал, что Kotlin позволит вам расширить тип функции с помощью интерфейса. Это, по крайней мере, решает проблему объекта-оболочки, хотя очень плохо, что для фактической реализации этого интерфейса необходим класс. - person Laurence Gonsalves; 10.06.2017

Вот как вы можете заставить его работать, хотя я действительно не уверен, чего вы пытаетесь достичь с его помощью:

typealias Fun<T> = () -> T
typealias Step<T> = () -> (T)

typealias Step1 = Step<Fun<Step2>>
typealias Step2 = Step<Fun<Step3>>
typealias Step3 = Step<Unit>

fun step1(): Step1 {
    return {
        println("step 1")
        ::step2
    }
}

fun step2(): Step2 {
    return {
        println("step 2")
        ::step3
    }
}

fun step3(): Step3 {
    return { println("done") }
}
person Kirill Rakhman    schedule 10.06.2017
comment
Это не сработает, так как мне нужно, чтобы все шаги были одного типа. Кроме того, данный шаг не всегда имеет один и тот же следующий шаг или одинаковое количество последующих шагов (в зависимости от ввода), но этот подход кодирует всю длину процесса в типе. - person Laurence Gonsalves; 12.06.2017

Используйте Enum для реализации шаблона состояний с конечными состояниями и предпочитайте возвращать ненулевые значения. Перечисление может наследоваться от функции.

enum class Step : () -> Step {
    Step1 {
        override fun invoke() = Step2
    },
    Step2 {
        override fun invoke() = End
    },
    End {
        override fun invoke() = this
    }
}

fun work() {
    var step = Step.Step1
    while (step !== Step.End) {
        step = step()
    }
}
person BladeCoder    schedule 12.06.2017