Tengo una función que genera valores. Lo hace llamándose recursivamente a sí mismo, y cada vez que se alcanza el caso base, se emite un valor. Este es solo un ejemplo de juguete, el real es mucho más complicado y, por lo tanto, más difícil de transformar en algo no recursivo.
Me gustaría hacer esto como una secuencia. Pero hacer esto no se compilará en la línea que cedo, con el mensaje
Las funciones de suspensión solo se pueden llamar dentro del cuerpo coroutine
Aquí está el intento:
fun test1() = sequence { fun a(i: Int) { if (i > 4) { yield(i) // <- fails to compile } else { a(i + 1) a(i + 2) a(i + 3) } } a(1) } Si trato de agregar la palabra clave suspend a la función a , obtengo el error
Las funciones de suspensión restringidas solo pueden invocar funciones de suspensión de miembros o extensiones en su ámbito de rutina restringido
Puedo hacer que funcione, devolviendo todos los valores y haciendo un yieldAll. Sin embargo, el problema con esto es que necesita hacer todo el cálculo y mantener todos los resultados en la memoria antes de que se pueda usar la secuencia. Para mi caso, esto no funcionará porque me quedaría sin memoria, o incluso puede tener una cantidad casi infinita de resultados donde solo quiero tomar algunos.
Así que esto funciona, pero no es óptimo.
fun test2() = sequence { fun a(i: Int): List<Int> { if (i > 4) { return listOf(i) } else { return listOf( a(i + 1), a(i + 2), a(i + 3) ).flatten() } } yieldAll(a(1)) }¿Alguna idea sobre cómo combinar secuencias con una función recursiva, lo que me permite generar valores sin precomputar y asignar memoria para todos ellos?
¿Qué tal hacer a devolución en una Sequence perezosa?
fun a(i: Int): Sequence<Int> = sequence { if (i > 4) { // println("yielding $i") yield(i) } else { yieldAll(a(i + 1)) // this yieldAll is lazy yieldAll(a(i + 2)) yieldAll(a(i + 3)) } } fun test1() = a(1) Si descomentas el println y recorres la secuencia, verás que es perezoso:
for (i in test1()) { println("printing $i") }Producción:
yielding 5 printing 5 yielding 6 printing 6 yielding 7 printing 7 yielding 5 printing 5 yielding 6 printing 6 yielding 5 printing 5 yielding 6 printing 6 yielding 7 printing 7 ...Puede convertir la función interna en una función de suspensión que es una extensión de SequenceScope. El generador de secuencias utiliza un tipo especial de corrutina que se limita a llamar únicamente a las extensiones de función de suspensión de SequenceScope. Presumiblemente, esto se debe a que las secuencias están destinadas a usarse solo como iterables sincrónicos y sin suspensión. Dado que yield() es una de estas funciones de suspensión de SequenceScope, debe hacer que su función también sea una función de extensión de suspensión.
fun test1() = sequence { suspend fun SequenceScope<Int>.a(i: Int) { if (i > 4) { yield(i) } else { a(i + 1) a(i + 2) a(i + 3) } } a(1) }