Scala是否与C#yield相当?

时间:2009-10-31 22:03:42

标签: c# scala yield

我是Scala的新手,据我所知,Scala中的产量与C#中的产量不同,它更像是选择。

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只是语法糖,可以很容易地在.Net中编写迭代器(IEnumerable<T>,类似于Java中的Iterable)。作为迭代器,它被懒惰地评估。

更新II:我可能在这里错了,但我认为C#中的整个收益点是你不必编写更高阶的函数。例如。您可以编写常规for循环或使用select / map / filter / where之类的方法,而不是传入将遍历序列的函数。

E.g。 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 个答案:

答案 0 :(得分:10)

劫持 yield 这个词会分散其惯常意图:作为coroutine中的进入/退出标记。上例中的C#BreadthFirstIterator似乎在其协同感中使用yield;在yield返回一个值后,对BreadthFirstIterator的{​​{1}}的下一次调用将继续调用IEnumerable之后的下一个语句。

在C#中,yieldcoupled to the idea of iteration而不是更一般的控制流语句,但在该有限域内,其行为是协程的行为。 Scala的分隔的延续可以允许人们定义协同程序。在那之前,Scala缺乏这样的能力,特别是考虑到yield的替代意义。

答案 1 :(得分:4)

是的,你可能想看看这个问题的答案: What is Scala's yield?

以下是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不会以相同的方式运行,但这是因为它们非常不同,所以如果你想将这个广度作为特性,那么你可以调用{{ 1}}和map()filter方法,就像在C#中一样,但这个特性将有助于解决如何遍历集合的问题。

答案 2 :(得分:4)

我认为答案(除非2.8的变化)是答案是否定的,Scala没有类似于C#的写入迭代器(IEumerable或Iterable的实现)的语法糖。

但是,在Scala中,您可以通过将函数传递给它将遍历遍历中的每个项目的遍历来实现类似的结果。这种方法也可以在C#中以相同的方式实现。

以下是我在不使用yield的情况下在C#中编写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);
});

答案 3 :(得分:3)

尽管Scala有一个关键字yield,但它与C#yield完全不同,Ruby的yield与两者都不同。它似乎是一个过度使用的关键字。在C#中使用yield看起来非常有限。

要在Scala中执行相同操作,您可以定义自己的高阶函数。在英语中,这意味着将函数作为参数的函数。

要采用Microsoft's example,这是一个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。

答案 4 :(得分:3)

您可以在Scala&gt; = 2.8中使用生成器的分隔连续实现来执行此操作。您将需要continuations plugin,然后是这些内容,

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

答案 5 :(得分:2)

如前所述,您可以使用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( () )
      ()
    }

}

答案 6 :(得分:0)

来自C#背景并调试了hotzen的Scala代码(适用于Scala 2.11.6),我必须说这个延续用法接近于C#-yield等价物。如果需要多个Generators,运行所有相同的方法或者可能分布在不同的方法上,我不知道continuation是否仍然会起到相似的作用,但我很高兴继续确实存在,这样我就不会被迫使用多个线程来实现类似的,或传递回拨。