yield
is mostly used in a for-yield loop to produce a new same-type collection. For example:
scala> val a = Array(2,3,5)
a: Array[Int] = Array(2, 3, 5)
scala> val result = for (elem <- a) yield 2 * elem
result: Array[Int] = Array(4, 6, 10)
This all works fine, the for loop takes an array and returns an array.
But then I noticed this:
scala> 1 to 10
res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
This generates a range type collection, but when you use this in conjunction with for-yield loop, this happened:
scala> for (i <- (1 to 10)) yield i + 2
res2: scala.collection.immutable.IndexedSeq[Int] = Vector(3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
Type that comes in is range, but the type it sends out is Vector. Why is this happenning? Am I missing anything?
Have a look at Range
:
class Range extends AbstractSeq[Int] with IndexedSeq[Int] with CustomParallelizable[Int, ParRange] with Serializable
Then the signature of map
:
def map[B](f: (A) ⇒ B): IndexedSeq[B]
The reason for this is that Range
is actually a sugared IndexedSeq
, all it adds on top is range specific behaviour:
Range.Inclusive
, Range.Exclusive
etc.
The reason why map
returns an IndexedSeq
is likely a compiler limitation, as it cannot predict the type of the Range
that results from the map
operation.
No, you're not missing anything. Take a look at the signature for map
in Range.
def map[B](f: (A) ⇒ B): IndexedSeq[B]
That is why it's producing the values that you see. Range
itself "is a" IndexedSeq
.
Why do I talk about map
when discussing a for-comprehension? For comprehensions are syntactic sugar for compiler transformations which utilizie map
, flatMap
and filter
under the hood (amongst other things.) So even if you just yield what you put in, you're calling a map
with identity
.
Also note, as to the Vector
portion of why this would happen...
IndexedSeq
is a trait. If you were to look at the source code for this trait here, the companion object produces a Vector
from the newBuilder[A]
method:
object IndexedSeq extends SeqFactory[IndexedSeq] {
override lazy val ReusableCBF =
scala.collection.IndexedSeq.ReusableCBF.asInstanceOf[GenericCanBuildFrom[Nothing]]
class Impl[A](buf: ArrayBuffer[A]) extends AbstractSeq[A] with IndexedSeq[A] with Serializable {
def length = buf.length
def apply(idx: Int) = buf.apply(idx)
}
def newBuilder[A]: Builder[A, IndexedSeq[A]] = Vector.newBuilder[A]
implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, IndexedSeq[A]] =
ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]]
}
Range has to have a fixed step between its values. Since it's impossible to infer that whatever yield returns will be a Range, the collection is made so that map
is defined as to return an IndexedSeq, i.e. behave like an IndexedSeq which it overrides.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With