5

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?

windweller
  • 2,365
  • 5
  • 33
  • 56
  • This is just wrong: “`yield` is mostly used in a for-yield loop to produce a new same-type collection.” – Jean-Philippe Pellet Dec 28 '13 at 18:45
  • Well..that is the only case I've seen with `yield`..@Jean-PhilippePellet I assume there should be more to the meaning of `yield` and its usage – windweller Dec 28 '13 at 18:48
  • @WindDweller Much more. You use for-comprehensions for monadic flow control and composition. Take a look at how you'd use `Future` with a for-comprehension and it'll blow your mind. Also have a look at `Try` with it's ability to handle exceptions. – wheaties Dec 28 '13 at 18:54
  • sort of duplicate: http://stackoverflow.com/questions/13130458/why-is-a-range-transformed-to-a-vector-after-map-operation – Denis Tulskiy Dec 28 '13 at 19:00
  • @WindDweller: the second question should be moved into a separate one. although you will get similar answers, it will be less confusing. – Denis Tulskiy Dec 28 '13 at 19:03
  • @DenisTulskiy I will. I just didn't want to throw too many `yield` questions. – windweller Dec 28 '13 at 19:04

3 Answers3

4

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]]
}
wheaties
  • 35,646
  • 15
  • 94
  • 131
  • I was thinking if `Vector` is the "go-to" type of `IndexedSeq`, and this means whenever a `IndexSeq` is constructed, it constructs as a `Vector`. So is it true that the `flatMap` operation actually constructs something new, disregarding the fact the programmer put in a `Range` type collection? – windweller Dec 28 '13 at 19:24
  • If `flatMap` is applicable to the outcome of the for-comprehension, then yes. – wheaties Dec 28 '13 at 19:24
  • @WindDweller The `flatMap` on `Range` could not return a `Range` as a result because the function you provide could do anything with the input and is not constrained to return equally spaced values, so the result cannot be statically guaranteed to be a `Range`. – Tim Destan Dec 28 '13 at 20:56
  • You are right @TimDestan `Range` only stores a head, tail, and increment. It seems like `flatMap` will correctly return `Array`, `List`, or any other type, except `Range` – windweller Dec 28 '13 at 22:03
4

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.

flavian
  • 28,161
  • 11
  • 65
  • 105
  • Can you be more specific about `map` operation? It seems to me that the only operation I've used is "+" and it shouldn't be a `map` operation. – windweller Dec 28 '13 at 18:52
  • Haha this is interesting. I rewrote a for-loop in a `flatMap` manner and I got Vector type as well. Even in Scala-doc, `flatMap` returns `IndexedSeq`, which is a trait both `Vector` and `Range` share. It might totally be a complier limitation because it seems like when a specific sequence type is missing, `Vector` is applied in default. – windweller Dec 28 '13 at 19:19
  • 1
    What `Range` could be produced if your yield was, say, `i * 2` instead of `i + 2`? Furthermore, to make `i + 2` example produce a `Range`, it would have to get all the results and see that they were contiguous values and then build a Range for their starting and ending values. – Randall Schulz Dec 28 '13 at 20:01
  • Actually, now that I think of it, `Range` need not be contiguous. But they do have to have uniform interval between values (e.g., `1 to 20 by 3`), so there are still many `yield` expressions whose entire results could not be represented by a `Range`. – Randall Schulz Dec 28 '13 at 21:04
2

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.

Denis Tulskiy
  • 19,012
  • 6
  • 50
  • 68