I understand Ruby and Python\'s yield. What does Scala\'s yield do?
问题:
回答1:
It is used in sequence comprehensions (like Python\'s list-comprehensions and generators, where you may use yield
too).
It is applied in combination with for
and writes a new element into the resulting sequence.
Simple example (from scala-lang)
/** Turn command line arguments to uppercase */
object Main {
def main(args: Array[String]) {
val res = for (a <- args) yield a.toUpperCase
println(\"Arguments: \" + res.toString)
}
}
The corresponding expression in F# would be
[ for a in args -> a.toUpperCase ]
or
from a in args select a.toUpperCase
in Linq.
Ruby\'s yield
has a different effect.
回答2:
I think the accepted answer is great, but it seems many people have failed to grasp some fundamental points.
First, Scala\'s for
comprehensions are equivalent to Haskell\'s do
notation, and it is nothing more than a syntactic sugar for composition of multiple monadic operations. As this statement will most likely not help anyone who needs help, let\'s try again… :-)
Scala\'s for
comprehensions is syntactic sugar for composition of multiple operations with map, flatMap
and filter
. Or foreach
. Scala actually translates a for
-expression into calls to those methods, so any class providing them, or a subset of them, can be used with for comprehensions.
First, let\'s talk about the translations. There are very simple rules:
This
for(x <- c1; y <- c2; z <-c3) {...}
is translated into
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
This
for(x <- c1; y <- c2; z <- c3) yield {...}
is translated into
c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
This
for(x <- c; if cond) yield {...}
is translated on Scala 2.7 into
c.filter(x => cond).map(x => {...})
or, on Scala 2.8, into
c.withFilter(x => cond).map(x => {...})
with a fallback into the former if method
withFilter
is not available butfilter
is. Please see the section below for more information on this.This
for(x <- c; y = ...) yield {...}
is translated into
c.map(x => (x, ...)).map((x,y) => {...})
When you look at very simple for
comprehensions, the map
/foreach
alternatives look, indeed, better. Once you start composing them, though, you can easily get lost in parenthesis and nesting levels. When that happens, for
comprehensions are usually much clearer.
I\'ll show one simple example, and intentionally omit any explanation. You can decide which syntax was easier to understand.
l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))
or
for {
sl <- l
el <- sl
if el > 0
} yield el.toString.length
withFilter
Scala 2.8 introduced a method called withFilter
, whose main difference is that, instead of returning a new, filtered, collection, it filters on-demand. The filter
method has its behavior defined based on the strictness of the collection. To understand this better, let\'s take a look at some Scala 2.7 with List
(strict) and Stream
(non-strict):
scala> var found = false
found: Boolean = false
scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9
scala> found = false
found: Boolean = false
scala> Stream.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
The difference happens because filter
is immediately applied with List
, returning a list of odds -- since found
is false
. Only then foreach
is executed, but, by this time, changing found
is meaningless, as filter
has already executed.
In the case of Stream
, the condition is not immediatelly applied. Instead, as each element is requested by foreach
, filter
tests the condition, which enables foreach
to influence it through found
. Just to make it clear, here is the equivalent for-comprehension code:
for (x <- List.range(1, 10); if x % 2 == 1 && !found)
if (x == 5) found = true else println(x)
for (x <- Stream.range(1, 10); if x % 2 == 1 && !found)
if (x == 5) found = true else println(x)
This caused many problems, because people expected the if
to be considered on-demand, instead of being applied to the whole collection beforehand.
Scala 2.8 introduced withFilter
, which is always non-strict, no matter the strictness of the collection. The following example shows List
with both methods on Scala 2.8:
scala> var found = false
found: Boolean = false
scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9
scala> found = false
found: Boolean = false
scala> List.range(1,10).withFilter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
This produces the result most people expect, without changing how filter
behaves. As a side note, Range
was changed from non-strict to strict between Scala 2.7 and Scala 2.8.
回答3:
Yes, as Earwicker said, it\'s pretty much the equivalent to LINQ\'s select
and has very little to do with Ruby\'s and Python\'s yield
. Basically, where in C# you would write
from ... select ???
in Scala you have instead
for ... yield ???
It\'s also important to understand that for
-comprehensions don\'t just work with sequences, but with any type which defines certain methods, just like LINQ:
- If your type defines just
map
, it allowsfor
-expressions consisting of a single generator. - If it defines
flatMap
as well asmap
, it allowsfor
-expressions consisting of several generators. - If it defines
foreach
, it allowsfor
-loops without yield (both with single and multiple generators). - If it defines
filter
, it allowsfor
-filter expressions starting with anif
in thefor
expression.
回答4:
Unless you get a better answer from a Scala user (which I\'m not), here\'s my understanding.
It only appears as part of an expression beginning with for
, which states how to generate a new list from an existing list.
Something like:
var doubled = for (n <- original) yield n * 2
So there\'s one output item for each input (although I believe there\'s a way of dropping duplicates).
This is quite different from the \"imperative continuations\" enabled by yield in other languages, where it provides a way to generate a list of any length, from some imperative code with almost any structure.
(If you\'re familiar with C#, it\'s closer to LINQ\'s select
operator than it is to yield return
).
回答5:
The keyword yield
in Scala is simply syntactic sugar which can be easily replaced by a map
, as Daniel Sobral already explained in detail.
On the other hand, yield
is absolutely misleading if you are looking for generators (or continuations) similar to those in Python. See this SO thread for more information: What is the preferred way to implement 'yield' in Scala?
回答6:
Consider the following for-comprehension
val A = for (i <- Int.MinValue to Int.MaxValue; if i > 3) yield i
It may be helpful to read it out loud as follows
\"For each integer i
, if it is greater than 3
, then yield (produce) i
and add it to the list A
.\"
In terms of mathematical set-builder notation, the above for-comprehension is analogous to
which may be read as
\"For each integer , if it is greater than , then it is a member of the set .\"
or alternatively as
\" is the set of all integers , such that each is greater than .\"
回答7:
Yield is similar to for loop which has a buffer that we cannot see and for each increment, it keeps adding next item to the buffer. When the for loop finishes running, it would return the collection of all the yielded values. Yield can be used as simple arithmetic operators or even in combination with arrays. Here are two simple examples for your better understanding
scala>for (i <- 1 to 5) yield i * 3
res: scala.collection.immutable.IndexedSeq[Int] = Vector(3, 6, 9, 12, 15)
scala> val nums = Seq(1,2,3)
nums: Seq[Int] = List(1, 2, 3)
scala> val letters = Seq(\'a\', \'b\', \'c\')
letters: Seq[Char] = List(a, b, c)
scala> val res = for {
| n <- nums
| c <- letters
| } yield (n, c)
res: Seq[(Int, Char)] = List((1,a), (1,b), (1,c), (2,a), (2,b), (2,c), (3,a), (3,b), (3,c))
Hope this helps!!
回答8:
val aList = List( 1,2,3,4,5 )
val res3 = for ( al <- aList if al > 3 ) yield al + 1
val res4 = aList.filter(_ > 3).map(_ + 1)
println( res3 )
println( res4 )
These two pieces of code are equivalent.
val res3 = for (al <- aList) yield al + 1 > 3
val res4 = aList.map( _+ 1 > 3 )
println( res3 )
println( res4 )
These two pieces of code are also equivalent.
Map is as flexible as yield and vice-versa.
回答9:
yield is more flexible than map(), see example below
val aList = List( 1,2,3,4,5 )
val res3 = for ( al <- aList if al > 3 ) yield al + 1
val res4 = aList.map( _+ 1 > 3 )
println( res3 )
println( res4 )
yield will print result like: List(5, 6), which is good
while map() will return result like: List(false, false, true, true, true), which probably is not what you intend.