Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scala "yield" doesn't produce the right type

Tags:

scala

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?

like image 364
windweller Avatar asked Dec 28 '13 18:12

windweller


3 Answers

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.

like image 121
flavian Avatar answered Oct 27 '22 04:10

flavian


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]]
}
like image 34
wheaties Avatar answered Oct 27 '22 04:10

wheaties


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.

like image 40
Denis Tulskiy Avatar answered Oct 27 '22 06:10

Denis Tulskiy