8

Following Scala courses on Coursera, Martin Odersky showed an example code which is:

1 to 5 map ( i => i*i )

And he said the Range gets transformed to a Vector because they share the same interface (IndexedSeq) and the result could not be represented as a Range (it was more clear in its example since he generated a pair which is not representable as a Range).

I'm not sure to understand because I think he said previously that in a for expression the 1st generator will determine the kind of element that will be yielded, and it seems not always true, at least for Range.

And I'm not sure to understand why the output is Vector, because Vector may not be the only other one implementation that can represent the result computed above.

Can someone help me understand this part please?

om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419

3 Answers3

10

map secretly takes a CanBuildFrom as an implicit argument. Its job is to produce a new collection given the one you've already got (and the type of the contents). Since Range can't contain arbitrary stuff--not even arbitrary integers--there is no CanBuildFrom that produces a Range. The most specific supertype of Range that does have a CanBuildFrom is IndexedSeq. The collection that is actually built by this is a Vector.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • 1
    So you mean Scala implicitly imports by default some implicit CanBuildFroms and that I could eventually override them or something? Is it possible, just to understand, to produce something else than a Vector in this case? – Sebastien Lorber Oct 29 '12 at 22:36
  • 1
    @SebastienLorber - Indeed you can! Look for `breakOut` as perhaps the most convenient way to do it if some other implicit already could be applicable, e.g. http://stackoverflow.com/questions/2592024 – Rex Kerr Oct 29 '12 at 22:38
  • yes, if you explicitly provide a valid builder you can get another collection as a result – Arjan Oct 29 '12 at 22:38
  • ANd what if we use a CanBuildFrom that produces something which does not provide monadic operations, can it be a problem in a for-comprehension? – Sebastien Lorber Oct 29 '12 at 23:03
2

As I'm sure Martin also explained, for comprehensions correspond to (are translated into) chained invocations of the map and flatMap methods (and foreach if you don't use yield).

The reason why it generally results in a value of the type of the first generator is that map and flatMap generally return the same type as their receiver (map on a List returns a List, etc.).

Now the problem with Ranges is that they cannot represent things that are not regular sequences of integers. As a consequence, the return type of map and flatMap as defined for Range cannot be Range. The next best match is Vector, the prototypical implementation of an indexed sequence.

(If you look at the source code or even the Scala doc page I linked to, you will see that it is a little more complicated that just the return type, but conceptually, that is the reason. Edit: ...and now Rex Kerr just dropped the CanBuildFrom bomb.)

Philippe
  • 9,582
  • 4
  • 39
  • 59
1

Vector is the default implementation for IndexedSeq. The map cannot be represented as a Range since the Range class is designed to contain a series of numbers that can be represented by a start, stop, and step value (similar to range in Python). The API docs specify that it's a special case of IndexSeq.

We can see 1 to 5 map { i => i * i } will get us a container of values (1, 4, 9, 16, 25). We can get a start and stop, but no constant step value.

adelbertc
  • 7,270
  • 11
  • 47
  • 70