Имеет ли Scala эквивалент С# yield?
Я новичок в Scala, и из того, что я понимаю, выход в Scala не похож на выход в С#, он больше похож на select.
Есть ли у Scala что-то похожее на С# yield? С# урожай отлично, потому что он очень удобен для написания итераторов.
Обновление: здесь пример псевдокода из С#, который я хотел бы реализовать в 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, он возвращает итератор, так что вызывающие могут пересекать график, используя регулярный цикл, например:
graph.BreadthFirstIterator().foreach( n => Console.WriteLine( n ) );
В С# выход - это просто синтаксический сахар, чтобы упростить запись итератора (IEnumerable<T>
в .Net, аналогично Iterable
в Java). Как итератор, его оценивают лениво.
Обновление II: Я могу ошибаться здесь, но я думаю, что вся точка урожая в С# заключается в том, что вам не нужно писать функцию более высокого порядка. Например. вы можете написать регулярный цикл или использовать метод, например 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))
.
Ответы
Ответ 1
Я думаю, что ответ (запрет на изменения в 2.8) заключается в том, что ответ отрицательный, Scala не имеет синтаксического сахара, подобного выводу С# для записи итераторов (реализации IEumerable или Iterable).
Однако в Scala вы могли бы получить аналогичный результат, передав функцию в обход, который он будет вызывать для каждого элемента в обходе. Этот подход также может быть реализован таким же образом в С#.
Вот как я бы написал Traverse в С# без использования урока:
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);
});
Ответ 2
Угнание этого слова здесь отвлекает от его обычного намерения: как маркер входа/выхода в coroutine. С# BreadthFirstIterator
в приведенном выше примере, по-видимому, использует yield
в своем сопроцессе; после того, как значение возвращается yield
, следующий вызов активного BreadthFirstIterator
IEnumerable
будет продолжен следующим оператором после yield
.
В С# yield
связанный с идеей итерации, а не как более общий оператор потока управления, но внутри этого ограниченного домена его поведение - поведение сопрограммы. Scala Разграниченные продолжения могут позволять определять сопрограммы. До тех пор Scala испытывает недостаток в такой возможности, особенно учитывая его альтернативный смысл для yield
.
Ответ 3
Да, да, вы можете посмотреть на этот вопрос для ответа:
Что такое Scala выход?
Вот документы из Scala для этого типа конструкции:
http://www.scala-lang.org/node/111
UPDATE:
В этом блоге рассказывается о выходе С# и Scala:
http://hestia.typepad.com/flatlander/2009/01/scala-for-c-programmers-part-1-mixins-and-traits.html
Он подробно рассказывает о том, как расширения используются для работы IENumerable по сравнению с использованием черт в Scala.
Итак, вы правы, что выход не будет работать таким же образом в Scala как С#, но это потому, что они очень разные, поэтому, если вы хотите сделать это BreadthFirst как показатель, вы можете вызвать map()
и filter
и foreach
, как и в С#, но эта черта поможет решить проблему перемещения коллекции.
Ответ 4
Даже если Scala имеет ключевое слово yield
, оно сильно отличается от С# yield
, а Ruby yield
отличается от обоих. Похоже, это слишком сильно используемое ключевое слово. Использование yield
в С# кажется очень ограниченным с первого взгляда.
Чтобы сделать то же самое в 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, С#: 0
Update:
Для вашего примера с добавлением напишите traverse
, который делает обход, который вы хотите, не задумываясь о том, как вы собираетесь его использовать. Затем добавьте дополнительный параметр, добавив (f(Node) => Any)
после списка параметров traverse
, например
def traverse(node:Node, maxDepth:Int)(f(Node) => Any)) { ... }
В точке traverse
, где у вас есть значение, вы бы yield
с в С#, вызовите 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.
Ответ 5
Вы можете сделать это в 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)
}
}
}
}
Ответ 6
Как уже упоминалось, вы можете создать генератор, используя плагин продолжения, чтобы создать доход, который ведет себя как С#:
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( () )
()
}
}
Ответ 7
Исходя из фона С# и отлаживая код Scala от hotzen (адаптированный к Scala 2.11.6), я должен сказать, что это использование продолжений приближается к эквиваленту С# -уровня. Я не знаю, будут ли продолжения по-прежнему работать аналогично, если потребуется несколько генераторов, все они будут работать в одних и тех же методах или, возможно, будут распространяться по разным методам, но я счастлив, что продолжения существуют, поэтому я не вынужден работать с несколькими потоками для достижения аналогично, или пройти по обратным вызовам.