2010-02-04 9 views
27

¿Cómo puede uno poner en práctica usando C# yield return continuaciones Scala? Me gustaría poder escribir Scala Iterator s en el mismo estilo. Una puñalada está en los comentarios en this Scala news post, pero no funciona (intenté utilizar la versión beta de Scala 2.8.0). Las respuestas en un related question sugieren que esto es posible, pero aunque he estado jugando con continuaciones delimitadas por un tiempo, parece que no puedo entender exactamente cómo hacerlo.rendimiento de ejecución (retorno de rendimiento) usando Scala continuaciones

+0

Lo que no funciona de ese ejemplo? No compila, o no produce los resultados esperados? Se menciona que, para que funcione, podría ser necesario tener un "foreach" consciente de CPS, pero, en cualquier caso, sería útil saber cuál es el problema. –

+0

No compila. – Yang

+2

Es posible que desee comprobar la respuesta de Miles Sabin a una pregunta similar que tuve http://stackoverflow.com/questions/2137619/scala-equivalent-to-python-generators/2146456#2146456. No estoy seguro de que eso te acerque más. – huynhjl

Respuesta

41

Antes de presentar las continuaciones que se necesita construir algunas infraestructuras. A continuación se muestra un trampoline que opera en los objetos Iteration. Una iteración es un cálculo que pueden o bien Yield un nuevo valor o puede ser Done.

sealed trait Iteration[+R] 
case class Yield[+R](result: R, next:() => Iteration[R]) extends Iteration[R] 
case object Done extends Iteration[Nothing] 

def trampoline[R](body: => Iteration[R]): Iterator[R] = { 
    def loop(thunk:() => Iteration[R]): Stream[R] = { 
    thunk.apply match { 
     case Yield(result, next) => Stream.cons(result, loop(next)) 
     case Done => Stream.empty 
    } 
    } 
    loop(() => body).iterator 
} 

La cama elástica utiliza un bucle interno que convierte la secuencia de Iteration objetos en un Stream. A continuación, obtener una Iterator llamando iterator en el objeto de flujo resultante. Al usar un Stream, nuestra evaluación es floja; no evaluamos nuestra próxima iteración hasta que se necesite.

La cama elástica puede ser utilizado para construir un iterador directamente.

val itr1 = trampoline { 
    Yield(1,() => Yield(2,() => Yield(3,() => Done))) 
} 

for (i <- itr1) { println(i) } 

Eso es bastante horrible para escribir, por lo que vamos a utilizar continuaciones delimitados para crear nuestros objetos Iteration automáticamente.

usamos el shift y reset operadores para romper el cálculo arriba en Iteration s, a continuación, utilizar trampoline para convertir los Iteration s en un Iterator.

import scala.continuations._ 
import scala.continuations.ControlContext.{shift,reset} 

def iterator[R](body: => Unit @cps[Iteration[R],Iteration[R]]): Iterator[R] = 
    trampoline { 
    reset[Iteration[R],Iteration[R]] { body ; Done } 
    } 

def yld[R](result: R): Unit @cps[Iteration[R],Iteration[R]] = 
    shift((k: Unit => Iteration[R]) => Yield(result,() => k(()))) 

Ahora podemos reescribir nuestro ejemplo.

val itr2 = iterator[Int] { 
    yld(1) 
    yld(2) 
    yld(3) 
} 

for (i <- itr2) { println(i) } 

Mucho mejor!

Ahora aquí hay un ejemplo del C# reference page para yield que muestra un uso más avanzado. Los tipos pueden ser un poco difíciles de acostumbrar, pero todo funciona.

def power(number: Int, exponent: Int): Iterator[Int] = iterator[Int] { 
    def loop(result: Int, counter: Int): Unit @cps[Iteration[Int],Iteration[Int]] = { 
    if (counter < exponent) { 
     yld(result) 
     loop(result * number, counter + 1) 
    } 
    } 
    loop(number, 0) 
} 

for (i <- power(2, 8)) { println(i) } 
+0

me gustaría ver el salida de scalac -print para iterator, yld, y la asignación a itr2. ¿Podría alguien con el complemento agregar esto a la respuesta? – retronym

+0

Solo intentaba aplicar esto, así que tenía el código en funcionamiento. Vea http://gist.github.com/297230 para la salida (vaya al final). – huynhjl

+1

Cambiaría el nombre de 'iterator' a' yldIterator' o algo así, para evitar confusiones. :-) –

5

Me las arreglé para descubrir una manera de hacerlo, después de unas horas más de juego. Pensé que era más fácil de entender que todas las otras soluciones que he visto hasta ahora, aunque después aprecié mucho las soluciones de Rich y Miles'.

def loopWhile(cond: =>Boolean)(body: =>(Unit @suspendable)): Unit @suspendable = { 
    if (cond) { 
    body 
    loopWhile(cond)(body) 
    } 
} 

    class Gen { 
    var prodCont: Unit => Unit = { x: Unit => prod } 
    var nextVal = 0 
    def yld(i: Int) = shift { k: (Unit => Unit) => nextVal = i; prodCont = k } 
    def next = { prodCont(); nextVal } 
    def prod = { 
     reset { 
     // following is generator logic; can be refactored out generically 
     var i = 0 
     i += 1 
     yld(i) 
     i += 1 
     yld(i) 
     // scala continuations plugin can't handle while loops, so need own construct 
     loopWhile (true) { 
      i += 1 
      yld(i) 
     } 
     } 
    } 
    } 
    val it = new Gen 
    println(it.next) 
    println(it.next) 
    println(it.next) 
+2

¿Las continuaciones de Scala no pueden manejar while loops? ¡Ay! –

+0

De hecho. :(Esperemos que se trate de un trabajo en progreso, pero creo que las comprensiones definitivamente no son compatibles con el cambio, ya que eso implicaría desmantelar el mapa/foreach/etc. – Yang

+3

Ya no más. Llamar al código cps desde dentro loop es posible desde hace un tiempo. Sin embargo, las comprensiones aún no son compatibles (realmente no creo que ganen soporte nunca) –

Cuestiones relacionadas