Scala a-t-il un équivalent de C# yield?

Je suis nouveau à Scala, et d'après ce que j'ai compris le rendement en Scala n'est pas comme le rendement en C#, il est plus comme select.

est-ce que Scala a quelque chose de similaire au rendement de C#? Le rendement de C#est excellent parce qu'il rend l'écriture des itérateurs très facile.

mise à jour: voici un exemple de pseudo-code de C# que j'aimerais pouvoir implémenter 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;
      }
   }
}

ce code implémente un largeur première traversée d'un graphe, en utilisant le rendement, il renvoie un itérateur, de sorte que les appelants peuvent traverser le graphe en utilisant une boucle régulière, par exemple:

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

en C#, yield est juste du sucre syntaxique pour faciliter l'écriture d'un itérateur ( IEnumerable<T> en .Net, similaire à Iterable en Java). Comme un itérateur, évaluée paresseusement.

mise à Jour II: j'ai peut-être tort, mais je pense que le point de l'ensemble de rendement en C# est donc que vous n'avez pas à écrire une fonction d'ordre supérieur. Par exemple: vous pouvez écrire un régulier pour boucle ou utiliser une méthode comme select / map / filter / where au lieu de passer dans une fonction qui traverse alors la séquence.

E. G. graph.iterator().foreach(n => println(n)) au lieu de graph.iterator( n => println(n)) .

de cette façon, vous pouvez les enchaîner facilement, E. g graph.iterator().map(x => x.foo).filter(y => y.bar >= 2).foreach(z => println(z)) .

24
demandé sur Lii 2009-11-01 01:03:42

7 réponses

le détournement du mot yield distrait ici de son intention habituelle: comme marqueur d'entrée/sortie dans un coroutine . Le C# BreadthFirstIterator dans l'exemple ci-dessus semble utiliser yield dans son sens de coroutine; après qu'une valeur est retournée par yield , l'appel suivant à actif BreadthFirstIterator 's IEnumerable continuera avec l'énoncé suivant après yield .

In C#, yield est couplé à l'idée d'itération plutôt que d'être un énoncé de flux de contrôle plus général, mais dans ce domaine limité son comportement est celui d'une coroutine. Le de Scala délimité les continuations peut permettre de définir des coroutines. D'ici là, Scala n'a pas cette capacité, d'autant plus que sa signification alternative est yield .

10
répondu seh 2009-11-10 04:47:29

Oui, c'est fait, vous pouvez regarder cette question pour la réponse: Quel est le rendement de Scala?

Voici les docs de Scala pour ce type de construction: http://www.scala-lang.org/node/111

mise à jour:

ce blog parle de C # yield et Scala: http://hestia.typepad.com/flatlander/2009/01/scala-for-c-programmers-part-1-mixins-and-traits.html

il donne quelques détails sur la façon dont les extensions sont utilisées pour rendre le travail IENumerable par rapport à l'utilisation de Traits dans Scala.

donc, vous avez raison que le rendement ne fonctionnera pas de la même manière à Scala que C#, mais c'est parce qu'ils sont très différents, et donc si vous voulez faire ce pain D'abord comme Trait alors vous pouvez appeler le map() et les méthodes filter et foreach , tout comme vous le feriez dans C#, mais le trait aidera à résoudre le problème de la façon de traverser la collection.

4
répondu James Black 2017-05-23 12:30:28

je pense que la réponse (à moins de changements dans 2.8) est que la réponse est non, Scala n'a pas de sucre syntaxique similaire à C'S rendement pour écrire des itérateurs (implémentations IEumerable ou Iterable).

cependant, dans Scala vous pouvez obtenir un résultat similaire en passant dans une fonction à la traversée qu'il invoquerait sur chaque élément de la traversée. Cette approche pourrait également être mise en œuvre de la même façon au C#.

Voici comment J'écrirais Traverse en C # sans l'utilisation de yield:

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;
      }
   }
}

vous pourriez alors l'utiliser comme ceci:

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

ou comme ceci:

graph.BreadthFirstTraversal( n =>
{
   Console.WriteLine(n);
   DoSomeOtherStuff(n);
});
4
répondu Alex Black 2016-08-26 11:01:24

même si Scala a un mot-clé yield , il est tout à fait différent du C# yield , et yield de Ruby est différent des deux. Il semble être un mot-clé sauvagement surutilisé. L'utilisation de yield dans C# semble très limitée à première vue.

pour faire la même chose à Scala, vous pourriez définir votre propre fonction de haut ordre. En français, cela signifie une fonction qui prend une fonction comme paramètre.

prendre Exemple de Microsoft , voici une méthode 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))}
  }
}

Maintenant vous avez votre "iterator":

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

Notes:

  • Powers(2,8) est le même que Powers.apply(2,8) . C'est juste un compilateur truc.
  • cette méthode est définie avec deux listes de paramètres, qui peuvent prêter à confusion. Il vous permet juste de faire: Powers(2, 8){ println(_) } au lieu de Powers(2, 8, {println(_)})

Scala: 1, C#: 0


mise à Jour:

pour votre exemple ajouté, écrivez traverse qui fait la traversée que vous voulez sans penser à la façon dont vous allez l'utiliser. Ajouter ensuite un paramètre supplémentaire en ajoutant (f(Node) => Any) après la liste des paramètres traverse , par exemple

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

Au point traverse , où vous avez une valeur yield avec in C#, appelez f(yieldValue) .

Lorsque vous souhaitez utiliser cette "itérateur" appel traverse et passer à une fonction qui fait tout ce que vous voulez faire pour chaque élément de l'itérateur.

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

il s'agit d'un cas de base pour la" programmation fonctionnelle " et vous devez vous assurer que vous comprenez Qu'il est réussi avec Scala.

3
répondu Alex Neth 2009-11-05 20:21:38

vous pouvez le faire dans Scala >= 2.8 en utilisant une implémentation de générateurs en termes de continuations délimitées. Vous aurez besoin du suite plugin et puis quelque chose dans ce sens,

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)
      }
    }
  }
}
3
répondu Miles Sabin 2010-02-04 20:13:56

comme déjà mentionné vous pourriez créer un générateur en utilisant les continuations-plugin pour créer un rendement qui se comporte exactement comme 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( () )
      ()
    }

}
2
répondu hotzen 2010-04-16 18:47:23

venant d'un arrière-plan C# et ayant débogué le code Scala de hotzen(adapté à Scala 2.11.6), je dois dire que cette utilisation de continuations se rapproche de L'équivalent C# - yield. Je ne sais pas si les continuations fonctionneraient encore de la même manière si plusieurs générateurs étaient nécessaires, fonctionnant tous selon les mêmes méthodes ou éventuellement répartis sur différentes méthodes, mais je suis heureux que les continuations existent, de sorte que je ne suis pas forcé de travailler avec plusieurs fils pour obtenir similaire, ou passer le long les rappels.

0
répondu wolfgang grinfeld 2015-04-12 10:07:14