I'm new to Scala, and from what I understand yield in Scala is not like yield in C#, it is more like select.
Does Scala have something similar to C#'s yield? C#'s yield is great because it makes writing iterators very easy.
Update: here's a pseudo code example from C# I'd like to be able to implement in 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开发者_JAVA技巧 ) {
yield return node;
nextLevel.addRange( node.Children );
}
currentLevel = nextLevel;
}
}
}
This code implements an iterative breadth first traversal of a graph, using yield, it returns an iterator, so that callers can traverse the graph using a regular for loop, e.g.:
graph.BreadthFirstIterator().foreach( n => Console.WriteLine( n ) );
In C#, yield is just syntactic sugar to make it easy to write an iterator (IEnumerable<T>
in .Net, similar to Iterable
in Java). As an iterator, its evaluated lazily.
Update II: I could be wrong here, but I think the whole point of yield in C# is so that you don't have to write a higher order function. E.g. you can write a regular for loop or use a method like select
/map
/filter
/where
instead of passing in a function which will then traverse the sequence.
E.g. graph.iterator().foreach(n => println(n))
instead of graph.iterator( n => println(n))
.
This way you can chain them easily, e.g graph.iterator().map(x => x.foo).filter(y => y.bar >= 2).foreach(z => println(z))
.
The hijacking of the word yield here distracts from its usual intent: as an entry/exit marker in a coroutine. The C# BreadthFirstIterator
in the example above appears to use yield
in its coroutine sense; after a value is returned by yield
, the next call to active BreadthFirstIterator
's IEnumerable
will continue with the next statement after yield
.
In C#, yield
is coupled to the idea of iteration rather than being a more general control flow statement, but within that limited domain its behavior is that of a coroutine. Scala's delimited continuations may allow one to define coroutines. Until then, Scala lacks such a capability, especially given its alternate meaning for yield
.
Yes it does, you may want to look at this question for the answer: What is Scala's yield?
Here is the docs from Scala for this type of construct: http://www.scala-lang.org/node/111
UPDATE:
This blog talks about C# yield and Scala: http://hestia.typepad.com/flatlander/2009/01/scala-for-c-programmers-part-1-mixins-and-traits.html
He goes into some detail about how extensions are being used to make IENumerable work compared to using Traits in Scala.
So, you are correct that yield won't function the same way in Scala as C#, but that is because they are very different, and so if you want to do this BreadthFirst as a Trait then you can call the map()
and filter
and foreach
methods, just as you would in C#, but the trait will help solve the problem of how to traverse the collection.
I think the answer (barring changes in 2.8) is that the answer is no, Scala does not have syntactic sugar similar to C#'s yield to write iterators (implementations of IEumerable or Iterable).
However, in Scala you could instead achieve a similar result by passing in a function to the traversal which it would invoke on each item in the traversal. This approach could also be implemented in the same fashion in C#.
Here is how I'd write Traverse in C# without the use of yield:
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;
}
}
}
You could then use it like this:
graph.BreadthFirstTraversal( n => Console.WriteLine( n ) );
Or like this:
graph.BreadthFirstTraversal( n =>
{
Console.WriteLine(n);
DoSomeOtherStuff(n);
});
Even though Scala has a keyword yield
, it's quite different from the C# yield
, and Ruby's yield
is different from both. It seems to be a wildly overused keyword. The use of yield
in C# appears very limited at first glance.
To do the same in Scala, you could define your own high-order function. In English, that means a function that takes a function as a parameter.
To take Microsoft's example, here's a Scala method:
object Powers {
def apply(number:Int, exponent:Int) (f:(Double) => Any) = {
(new Range(1,exponent+1,1)).map{exponent => f(Math.pow(number, exponent))}
}
}
Now you have your "iterator":
scala> Powers(2,8){ println(_) }
2.0
4.0
8.0
16.0
32.0
64.0
128.0
256.0
Notes:
Powers(2,8)
is the same asPowers.apply(2,8)
. That's just a compiler trick.- This method is defined with two parameter lists, which might be confusing. It just allows you to do:
Powers(2, 8){ println(_) }
instead ofPowers(2, 8, {println(_)})
Scala: 1, C#: 0
Update:
For your just-added example, write traverse
that does the traversal you want without thinking about how you are going to use it. Then add an extra parameter by adding (f(Node) => Any)
after the traverse
parameter list, e.g.
def traverse(node:Node, maxDepth:Int)(f(Node) => Any)) { ... }
At the point in traverse
where you have a value you would yield
with in C#, call f(yieldValue)
.
When you want to use this "iterator," call traverse
and pass a function to it that does whatever it is you want to do for each element in the iterator.
traverse(node, maxDepth) { (yieldValue) =>
// this is f(yieldValue) and will be called for each value that you call f with
println(yieldValue)
}
This is a basic case for "functional programming" and you should make sure you understand it to be successful with Scala.
You can do this in Scala >= 2.8 using an implementation of generators in terms of delimited continuations. You'll need the continuations plugin and then something along these lines,
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)
}
}
}
}
As already mentioned you could create a Generator using the continuations-plugin to create a yield which is exactly behaving like 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( () )
()
}
}
Coming from a C# background and having debugged the Scala code from hotzen(adapted to Scala 2.11.6), I must say this continuations usage comes close to the C#-yield equivalent. I do not know if continuations still would function similarly if multiple Generators were needed, running all in the same methods or possibly spread over different methods, but I am happy continuations do exist, so that I am not forced to work with multiple threads to achieve similar, or pass along call-backs.
精彩评论