24

我是 Scala 的新手,据我了解,Scala 中的 yield 不像 C# 中的 yield,它更像是 select。

Scala 是否有类似于 C# 的 yield 的东西?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 中,类似于IterableJava)。作为一个迭代器,它的评估是惰性的。

更新二:我在这里可能是错的,但我认为 C# 中 yield 的全部意义在于你不必编写更高阶的函数。例如,您可以编写一个常规的 for 循环或使用类似 /// 的方法,selectmap不是传入一个将遍历序列的函数。filterwhere

例如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)).

4

7 回答 7

10

这里对yield一词的劫持分散了其通常的意图:作为协程中的进入/退出标记。上例中的 C#BreadthFirstIterator似乎yield在其协程意义上使用;在返回一个值之后,对 active的yield下一次调用将继续执行下一条语句。BreadthFirstIteratorIEnumerableyield

在 C# 中,yield与迭代的概念相结合,而不是更一般的控制流语句,但在这个有限的领域内,它的行为是协程的行为。Scala 的定界延续可能允许定义协程。在那之前,Scala 缺乏这样的能力,尤其是考虑到yield.

于 2009-11-10T04:47:29.170 回答
4

是的,你可能想看看这个问题的答案: Scala 的产量是多少?

以下是 Scala 提供的此类构造的文档: http ://www.scala-lang.org/node/111

更新:

此博客讨论 C# 产量和 Scala: http ://hestia.typepad.com/flatlander/2009/01/scala-for-c-programmers-part-1-mixins-and-traits.html

与在 Scala 中使用 Traits 相比,他详细介绍了如何使用扩展来使 IENumerable 工作。

所以,你是正确的,yield 在 Scala 中的功能与 C# 不同,但那是因为它们非常不同,所以如果你想将 BreadthFirst 作为 Trait 执行,那么你可以调用map()andfilterforeach方法,只是就像在 C# 中一样,但 trait 将有助于解决如何遍历集合的问题。

于 2009-10-31T23:02:20.837 回答
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);
});
于 2009-11-06T03:14:02.027 回答
3

您可以在 Scala >= 2.8 中使用根据分隔延续的生成器实现来执行此操作。您将需要continuations 插件,然后是这些方面的东西,

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)
      }
    }
  }
}
于 2010-02-04T20:13:56.383 回答
3

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

要在 Scala 中做同样的事情,您可以定义自己的高阶函数。在英语中,这意味着一个将函数作为参数的函数。

微软为例,这里有一个 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(_)})

斯卡拉:1,C#:0


更新:

对于您刚刚添加的示例,请编写traverse您想要的遍历而不考虑您将如何使用它。(f(Node) => Any)然后通过在参数列表之后添加一个额外的traverse参数,例如

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

traverse您拥有yieldC# 中的值的地方,调用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 中取得成功。

于 2009-11-05T05:30:49.120 回答
2

如前所述,您可以使用 continuations-plugin 创建一个生成器,以创建一个行为与 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( () )
      ()
    }

}
于 2010-04-16T18:47:23.233 回答
0

来自 C# 背景并从 hotzen 调试了 Scala 代码(适用于 Scala 2.11.6),我必须说这种延续用法接近 C#-yield 等价物。我不知道如果需要多个生成器,继续运行是否仍会类似地运行,以相同的方法运行或可能分布在不同的方法上,但我很高兴继续存在,因此我不会被迫使用多个线程来实现类似的,或传递回调。

于 2015-04-12T10:07:14.987 回答