remarks cref c# scala yield

cref - remarks c#



¿Tiene Scala un rendimiento equivalente a C#? (7)

Soy nuevo en Scala, y por lo que entiendo, el rendimiento en Scala no es como el rendimiento en C #, es más como selecto.

¿Tiene Scala algo similar al rendimiento de C #? El rendimiento de C # es excelente porque hace que escribir iteradores sea muy fácil.

Actualización: aquí hay un ejemplo de pseudo código de C # que me gustaría poder implementar en Scala:

public class Graph<T> { public IEnumerable<T> BreadthFirstIterator() { List<T> currentLevel = new List<T>(); currentLevel.add(_root); while ( currentLevel.count > 0 ) { List<T> nextLevel = new List<T>(); foreach( var node in currentLevel ) { yield return node; nextLevel.addRange( node.Children ); } currentLevel = nextLevel; } } }

Este código implementa un primer recorrido iterativo de la amplitud de una gráfica, utilizando el rendimiento, devuelve un iterador, de modo que las personas que llaman puedan atravesar la gráfica utilizando un bucle for regular, por ejemplo:

graph.BreadthFirstIterator().foreach( n => Console.WriteLine( n ) );

En C #, el rendimiento es solo azúcar sintáctica para facilitar la escritura de un iterador ( IEnumerable<T> en .Net, similar a Iterable en Java). Como iterador, es evaluado perezosamente.

Actualización II: podría estar equivocado aquí, pero creo que todo el punto de rendimiento en C # es para que no tenga que escribir una función de orden superior. Por ejemplo, puede escribir un bucle for regular o usar un método como select / map / filter / where lugar de pasar una función que luego atravesará la secuencia.

Por ejemplo, graph.iterator().foreach(n => println(n)) lugar de graph.iterator( n => println(n)) .

De esta manera puede encadenarlos fácilmente, por ejemplo, graph.iterator().map(x => x.foo).filter(y => y.bar >= 2).foreach(z => println(z)) .


Al provenir de un fondo de C # y haber depurado el código de Scala de hotzen (adaptado a Scala 2.11.6), debo decir que este uso de continuación se acerca al equivalente de rendimiento de C #. No sé si las continuaciones seguirían funcionando de manera similar si se necesitaran múltiples Generadores, ejecutando todos en los mismos métodos o posiblemente repartidos en diferentes métodos, pero estoy feliz de que existan continuaciones, por lo que no estoy obligado a trabajar con múltiples hilos para lograr similares, o pasar a lo largo de las devoluciones de llamada.


Aunque Scala tiene un yield palabra clave, es bastante diferente del yield C #, y el yield de Ruby es diferente de ambos. Parece ser una palabra clave muy usada en exceso. El uso del yield en C # parece muy limitado a primera vista.

Para hacer lo mismo en Scala, puede definir su propia función de alto orden. En inglés, eso significa una función que toma una función como parámetro.

Para tomar el ejemplo de Microsoft , aquí hay un método Scala:

object Powers { def apply(number:Int, exponent:Int) (f:(Double) => Any) = { (new Range(1,exponent+1,1)).map{exponent => f(Math.pow(number, exponent))} } }

Ahora tienes tu "iterador":

scala> Powers(2,8){ println(_) } 2.0 4.0 8.0 16.0 32.0 64.0 128.0 256.0

Notas:

  • Powers(2,8) es lo mismo que Powers.apply(2,8) . Eso es sólo un truco de compilación.
  • Este método se define con dos listas de parámetros, lo que puede ser confuso. Solo te permite hacer: Powers(2, 8){ println(_) } lugar de Powers(2, 8, {println(_)})

Scala: 1, C #: 0

Actualizar:

Para el ejemplo que acaba de agregar, escriba traverse que realice el recorrido que desea sin pensar en cómo lo va a usar. Luego agregue un parámetro adicional agregando (f(Node) => Any) después de la lista de parámetros de la traverse , por ejemplo

def traverse(node:Node, maxDepth:Int)(f(Node) => Any)) { ... }

En el punto de la traverse donde tiene un valor que obtendría en C #, llame a f(yieldValue) .

Cuando quiera usar este "iterador", llame a la traverse y pásele una función que haga lo que sea que quiera hacer para cada elemento del iterador.

traverse(node, maxDepth) { (yieldValue) => // this is f(yieldValue) and will be called for each value that you call f with println(yieldValue) }

Este es un caso básico para la "programación funcional" y debe asegurarse de entenderlo para tener éxito con Scala.


Como ya se mencionó, puede crear un generador utilizando el complemento de continuaciones para crear un rendimiento que se comporte exactamente como C #:

import scala.util.continuations._ object GenTest { val gen = new Generator[Int] { def produce = { yieldValue(1) yieldValue(2) yieldValue(3) Thread.sleep(1000) yieldValue(42) }} def main(args: Array[String]): Unit = { for (v <- gen) { println(v) } } } abstract class Generator[E] { var loopFn: (E => Unit) = null def produce(): Unit @cps[Unit] def foreach(f: => (E => Unit)): Unit = { loopFn = f reset[Unit,Unit]( produce ) } def yieldValue(value: E): Unit @cps[Unit] = shift { genK: (Unit => Unit) => loopFn( value ) genK( () ) () } }


Creo que la respuesta (salvo los cambios en 2.8) es que la respuesta es no, Scala no tiene azúcar sintáctica similar al rendimiento de C # para escribir iteradores (implementaciones de IEumerable o Iterable).

Sin embargo, en Scala, en cambio, podría lograr un resultado similar pasando una función al recorrido que invocaría en cada elemento del recorrido. Este enfoque también podría implementarse de la misma manera en C #.

Aquí es cómo escribiría Traverse en C # sin el uso de rendimiento:

public class Graph<T> { public void BreadthFirstTraversal( Action<T> f) { List<T> currentLevel = new List<T>(); currentLevel.add(_root); while ( currentLevel.count > 0 ) { List<T> nextLevel = new List<T>(); foreach( var node in currentLevel ) { f(node); nextLevel.addRange( node.Children ); } currentLevel = nextLevel; } } }

Entonces podrías usarlo así:

graph.BreadthFirstTraversal( n => Console.WriteLine( n ) );

O así:

graph.BreadthFirstTraversal( n => { Console.WriteLine(n); DoSomeOtherStuff(n); });


El secuestro de la palabra rendimiento aquí distrae de su intención habitual: como un marcador de entrada / salida en una coroutine . El C # BreadthFirstIterator en el ejemplo anterior parece usar el yield en su sentido común; después de que se devuelve un valor por yield , la próxima llamada a BreadthFirstIterator de BreadthFirstIterator activo continuará con la siguiente declaración después del yield .

En C #, el yield está acoplado a la idea de iteración en lugar de ser una declaración de flujo de control más general, pero dentro de ese dominio limitado su comportamiento es el de una coroutina. Las continuaciones delimitadas de Scala pueden permitir que uno defina coroutines. Hasta entonces, Scala carece de tal capacidad, especialmente dado su significado alternativo para el yield .


Puede hacer esto en Scala> = 2.8 usando una implementación de generadores en términos de continuaciones delimitadas. Necesitará el complemento de continuaciones y luego algo así,

import scala.continuations._ import scala.continuations.ControlContext._ object Test { def loopWhile(cond: =>Boolean)(body: =>(Unit @suspendable)): Unit @suspendable = { if (cond) { body loopWhile(cond)(body) } else () } abstract class Generator[T] { var producerCont : (Unit => Unit) = null var consumerCont : (T => Unit) = null protected def body : Unit @suspendable reset { body } def generate(t : T) : Unit @suspendable = shift { (k : Unit => Unit) => { producerCont = k if (consumerCont != null) consumerCont(t) } } def next : T @suspendable = shift { (k : T => Unit) => { consumerCont = k if (producerCont != null) producerCont() } } } def main(args: Array[String]) { val g = new Generator[Int] { def body = { var i = 0 loopWhile(i < 10) { generate(i) i += 1 } } } reset { loopWhile(true) { println("Generated: "+g.next) } } } }


Sí, es posible que desee consultar esta pregunta para obtener la respuesta: ¿Cuál es el rendimiento de Scala?

Aquí están los documentos de Scala para este tipo de construcción: http://www.scala-lang.org/node/111

ACTUALIZAR:

Este blog habla sobre el rendimiento de C # y Scala: http://hestia.typepad.com/flatlander/2009/01/scala-for-c-programmers-part-1-mixins-and-traits.html

Entra en algunos detalles sobre cómo se utilizan las extensiones para hacer que IENumerable funcione en comparación con el uso de Rasgos en Scala.

Por lo tanto, es correcto que el rendimiento no funcionará de la misma manera en Scala que en C #, pero eso se debe a que son muy diferentes y, por lo tanto, si desea hacer esto BreadthFirst como un rasgo, puede llamar al map() y filter y para cada método, como lo haría en C #, pero el rasgo ayudará a resolver el problema de cómo atravesar la colección.