Есть ли у Scala эквивалент C # yield? - PullRequest
24 голосов
/ 01 ноября 2009

Я новичок в Scala, и из того, что я понимаю, yield в Scala не похож на yield в C #, он больше похож на select.

Есть ли у Scala что-то похожее на выход C #? Выход C # велик, потому что он делает написание итераторов очень простым.

Обновление: вот пример псевдокода из C #, который я хотел бы реализовать в 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;
      }
   }
}

Этот код реализует итеративный обход графика в ширину, используя yield, он возвращает итератор, так что вызывающие абоненты могут проходить по графику с помощью регулярного цикла for, например ::

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

В C # yield является просто синтаксическим сахаром, облегчающим написание итератора (IEnumerable<T> в .Net, аналогично Iterable в Java). Как итератор, его оценивают лениво.

Обновление II: Я могу ошибаться, но я думаю, что весь смысл yield в C # заключается в том, что вам не нужно писать функцию более высокого порядка. Например. Вы можете написать регулярную форму для цикла или использовать метод, подобный select / map / filter / where вместо передачи функции, которая затем будет проходить последовательность.

например. graph.iterator().foreach(n => println(n)) вместо graph.iterator( n => println(n)).

Таким образом, вы можете легко их связать, например, graph.iterator().map(x => x.foo).filter(y => y.bar >= 2).foreach(z => println(z)).

Ответы [ 7 ]

10 голосов
/ 10 ноября 2009

Угон слова yield здесь отвлекает от его обычного намерения: в качестве маркера входа / выхода в сопрограмме . C # BreadthFirstIterator в вышеприведенном примере, похоже, использует yield в смысле сопрограммы; после того, как yield вернет значение, следующий вызов активного BreadthFirstIterator IEnumerable продолжится со следующего оператора после yield.

В C # yield является связанным с идеей итерации , а не является более общим оператором потока управления, но в пределах этой ограниченной области его поведение соответствует поведению сопрограммы. Scala с разделителями может позволить определять сопрограммы. До этого у Scala такой возможности нет, особенно учитывая ее альтернативное значение для yield.

4 голосов
/ 06 ноября 2009

Я думаю, что ответ (за исключением изменений в 2.8) заключается в том, что ответ - нет, у Scala нет синтаксического сахара, похожего на выход C # для написания итераторов (реализации IEumerable или Iterable).

Однако в Scala вы могли бы достичь аналогичного результата, передав функцию обходу, которую она будет вызывать для каждого элемента в обходе. Этот подход также может быть реализован таким же образом в C #.

Вот как я бы написал Traverse в C # без использования 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;
      }
   }
}

Вы можете использовать его так:

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

Или вот так:

graph.BreadthFirstTraversal( n =>
{
   Console.WriteLine(n);
   DoSomeOtherStuff(n);
});
4 голосов
/ 01 ноября 2009

Да, возможно, вы можете посмотреть на этот вопрос для ответа: Каков урожай Scala?

Вот документы из Scala для этого типа конструкции: http://www.scala -lang.org / узел / 111

UPDATE:

В этом блоге рассказывается о выходе C # и Scala: http://hestia.typepad.com/flatlander/2009/01/scala-for-c-programmers-part-1-mixins-and-traits.html

Он подробно расскажет о том, как используются расширения для обеспечения работы IENumerable по сравнению с использованием черт в Scala.

Итак, вы правы, что доходность не будет работать в Scala так же, как в C #, но это потому, что они очень разные, и поэтому, если вы хотите использовать BreadthFirst в качестве черты, вы можете вызвать map() и filter и foreach, как в C #, но эта особенность поможет решить проблему обхода коллекции.

3 голосов
/ 04 февраля 2010

Вы можете сделать это в Scala> = 2.8, используя реализацию генераторов в терминах продолжений с разделителями. Вам понадобится плагин продолжений , а затем что-то вроде этого,

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 голосов
/ 05 ноября 2009

Несмотря на то, что у Scala есть ключевое слово yield, оно довольно сильно отличается от C # yield, а Ruby's yield отличается от обоих. Кажется, это ключевое слово с чрезмерным использованием. На первый взгляд использование yield в C # выглядит очень ограниченным.

Чтобы сделать то же самое в Scala, вы можете определить свою собственную функцию высокого порядка. В английском это означает функцию, которая принимает функцию в качестве параметра.

Взять пример Microsoft , вот метод 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))}
  }
}

Теперь у вас есть «итератор»:

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

Примечания:

  • Powers(2,8) совпадает с Powers.apply(2,8). Это просто трюк с компилятором.
  • Этот метод определяется двумя списками параметров, что может сбивать с толку. Это просто позволяет вам сделать: Powers(2, 8){ println(_) } вместо Powers(2, 8, {println(_)})

Scala: 1, C #: 0


Обновление:

Для вашего только что добавленного примера напишите traverse, который выполняет обход, который вы хотите, не думая о том, как вы собираетесь его использовать. Затем добавьте дополнительный параметр, добавив (f(Node) => Any) после списка параметров traverse, например,

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

В точке в traverse, где у вас есть значение, которое вы бы yield использовали в C #, позвоните f(yieldValue).

Если вы хотите использовать этот «итератор», вызовите traverse и передайте ему функцию, которая делает то, что вы хотите сделать для каждого элемента в итераторе.

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

Это базовый случай «функционального программирования», и вы должны убедиться, что понимаете, что оно будет успешным в Scala.

2 голосов
/ 16 апреля 2010

Как уже упоминалось, вы можете создать Генератор, используя плагин продолжений, чтобы создать доход, который точно ведет себя как 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( () )
      ()
    }

}
0 голосов
/ 12 апреля 2015

Исходя из фона C # и отладки кода Scala из hotzen (адаптированного к Scala 2.11.6), я должен сказать, что использование этого продолжения близко к эквиваленту C # -производства. Я не знаю, будут ли продолжения функционировать аналогично, если бы потребовалось несколько Генераторов, работающих все в одних и тех же методах или, возможно, распределенных по разным методам, но я рад, что продолжения существуют, так что я не вынужден работать с несколькими потоками для достижения похожи или передают обратные вызовы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...