[英]Does Scala have an equivalent to C# 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
而不是傳入一個函數,然后遍歷序列。
例如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<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);
});
盡管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
,你有一個值,你會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。
您可以在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是否仍然會起到相似的作用,但我很高興繼續確實存在,這樣我就不會被迫使用多個線程來實現類似的,或傳遞回撥。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.