Scala是否与C#yield相当?
我是Scala的新手,据我所知,Scala中的收益与C#中的收益不同,它更像是选择。
Scala有类似于C#的收益吗? C#的收益很好,因为它使编写迭代器变得非常容易。
更新:这是来自C#的伪代码示例,我希望能够在Scala中实现:
public class Graph { public IEnumerable BreadthFirstIterator() { List currentLevel = new List(); currentLevel.add(_root); while ( currentLevel.count > 0 ) { List nextLevel = new List(); 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只是语法糖,可以很容易地编写迭代器(.Net中的IEnumerable
,类似于Java中的Iterable
)。 作为迭代器,它的评估很懒散。
更新II:我可能在这里错了,但我认为C#中的整个收益点是你不必编写更高阶函数。 例如,您可以编写常规for循环或使用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))
。
这里劫持单词yield会分散其惯常意图:作为协程中的入口/出口标记。 上例中的C# BreadthFirstIterator
似乎在其协同感中使用yield
; 在yield
返回一个值之后,下一次调用活动的BreadthFirstIterator
的IEnumerable
将继续执行yield
之后的下一个语句。
在C#中, yield
与迭代的思想相关联,而不是更一般的控制流语句,但在该有限域内,其行为是协程的行为。 Scala的分隔延续可能允许人们定义协同程序。 在那之前,Scala缺乏这样的能力,特别是考虑到它对yield
替代意义。
是的,你可能想看看这个问题的答案: Scala的收益率是多少?
以下是Scala针对此类构造的文档: http : //www.scala-lang.org/node/111
更新:
这篇博客讨论了C#yield和Scala: http : //hestia.typepad.com/flatlander/2009/01/scala-for-c-programmers-part-1-mixins-and-traits.html
他详细介绍了与在Scala中使用Traits相比,如何使用扩展来进行IENumerable工作。
所以,你是正确的,在Scala和C#中,yield不会以相同的方式起作用,但这是因为它们非常不同,所以如果你想将这个广度作为特性,那么你可以调用map()
和filter
和foreach
方法一样,就像你在C#中一样,但这个特性将有助于解决如何遍历集合的问题。
我认为答案(除非2.8的变化)是答案是否定的,Scala没有类似于C#的写入迭代器(IEumerable或Iterable的实现)的语法糖。
但是,在Scala中,您可以通过将函数传递给它将在遍历中的每个项目上调用的遍历来实现类似的结果。 这种方法也可以在C#中以相同的方式实现。
这是我如何在不使用yield的情况下在C#中编写Traverse:
public class Graph { public void BreadthFirstTraversal( Action f) { List currentLevel = new List (); currentLevel.add(_root); while ( currentLevel.count > 0 ) { List nextLevel = new List (); 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); });
尽管Scala具有关键字yield
,但它与C# yield
完全不同,而Ruby的yield
与两者不同。 它似乎是一个过度使用的关键字。 C#中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,C#:0
更新:
对于刚刚添加的示例,编写traverse
,执行您想要的遍历,而不考虑如何使用它。 然后通过在traverse
参数列表之后添加(f(Node) => Any)
来添加额外参数,例如
def traverse(node:Node, maxDepth:Int)(f(Node) => Any)) { ... }
在traverse
,您将在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。
您可以在Scala> = 2.8中使用生成器的分隔连续实现来执行此操作。 你将需要continuation插件 ,然后沿着这些线,
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) } } } }
如前所述,您可以使用continuations-plugin创建一个Generator来创建一个与C#完全相同的yield:
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( () ) () } }
来自C#背景并调试了hotzen的Scala代码(适用于Scala 2.11.6),我必须说这个延续用法接近于C#-yield等价物。 如果需要多个Generators,运行所有相同的方法或者可能分布在不同的方法上,我不知道continuation是否仍然会起到相似的作用,但我很高兴继续确实存在,这样我就不会被迫使用多个线程来实现类似的,或传递回拨。