Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

incredible implicit Array conversion in scala

According to Scaladoc, there is no method named map in Array class, but there is an implicit function implicit def intArrayOps (xs: Array[Int]): ArrayOps[Int] defined in scala.Predef. So you can apply map on Array(1,2,3,4) if you like. But What I am confused about is that the map result is of type Array[Int], not ArrayOps[Int]. Here is my test:

scala> val array = Array(1,2,3,4)
array: Array[Int] = Array(1, 2, 3, 4)

scala> array.map(x => x)
res18: Array[Int] = Array(1, 2, 3, 4)

scala> res18.isInstanceOf[Array[Int]]
res19: Boolean = true

scala> res18.isInstanceOf[scala.collection.mutable.ArrayOps[Int]]
warning: there wre 1 unchecked warnings; re-run with -unchecked for details
res20: Boolean = false
like image 307
爱国者 Avatar asked Nov 27 '11 09:11

爱国者


2 Answers

It indeed returns an array, as intended and as is convenient, there is no reason you would need an ArrayOps, it is intended only to provide extra methods to arrays. The doc is wrong.

The routine is actually not implemented in ArrayOps. As most collection methods, it is inherited from TraversableLike. And you see two map methods in the doc:

def map [B] (f: (T) ⇒ B): ArrayOps[B]
def map [B, That] (f: (T) ⇒ B)(implicit bf: CanBuildFrom[Array[T], B, That]): That

Only the second one exists (inherited from TraversableLike). It is intended to allow implementation of map in just one place (traversable like) while allways giving the best possible behavior. For instance, a String is a of Seq[Char], if you map with a function from character to character, you get a String, but if you map from collection to say Int, the result cannot be a String and it will just be a Seq. This is explained in much detail in the paper fighting the bit rot with types.

However, this makes for a very complex signature, which does not reflect the simplicity of using the method, and makes very poor documentation most of the time (you normally would have to chase to which CanBuildFrom in implicit scope would work). This was discussed in this most famous scala question of stack overflow. So the tool scaladoc was extended so that a simpler entry, corresponding to intended usage, may appear. If you look at the source of GenTraversableLike, where the routine is introduced, you will see the following in the scaladoc for map (and a similar one in many methods)

@usecase def map[B](f: A => B): $Coll[B]

Subtypes add in their doc @define Coll <className>, and map (among others) appears with the simplified signature, marked [Use case]. In the source of ArrayOps, there is a @define Coll ArrayOps where it should be Array.

like image 89
Didier Dupont Avatar answered Nov 17 '22 16:11

Didier Dupont


You can use the REPL with the -Xprint:typer option to see what's going on. Here is the output of the map method, reformatted for easier reading:

$ scala -Xprint:typer

scala> Array(1,2,3,4).map(x => x)
[[syntax trees at end of typer]]// Scala source: <console>
// some lines deleted
private[this] val res0: Array[Int] =
  scala.this.Predef.intArrayOps(scala.Array.apply(1, 2, 3, 4))
   .map[Int, Array[Int]]
     (( (x: Int) => x ))
     (scala.this.Array.canBuildFrom[Int](reflect.this.Manifest.Int));

So simplifying for package names here is what happens:

intArrayOps(Array(1,2,3,4)) // converts to ArrayOps
  .map[Int, Array[Int]]     // calls map with parameter lists below
    ((x:Int) => x)          // pass identity function as fisrt param
    (Array.canBuildFrom[Int]// pass builder for Array[Int] as second param
      (Manifest.Int))       // pass class manifest for Int
  1. So there is indeed a conversion to ArrayOps (first line). It returns ArrayOps[Int].
  2. The ArrayOps.map[Int, Array[Int]] method is then called on it. Then as didierd explain, the original signature for map - not the simplified signature - indicates that the return type inferred will be Array[Int]
like image 21
huynhjl Avatar answered Nov 17 '22 15:11

huynhjl