Why does the scaladoc say HashMap.toArray returns Array[A] instead of Array[(A,B)]?

StackOverflow https://stackoverflow.com/questions/17277985

  •  01-06-2022
  •  | 
  •  

Question

I was looking at the definition of toArray for hashmaps :

http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.HashMap

It has

toArray: Array[A]
def toArray[B >: (A, B)](implicit arg0: ClassTag[B]): Array[B]

I don't quite understand this - the first bit says you get an Array[A], but the second part says you get Array[B]? Neither of these are what I expect - Array[(A,B)]

When I check it myself :

scala> val x = scala.collection.mutable.HashMap[String, Int]()
x: scala.collection.mutable.HashMap[String,Int] = Map()

scala> x.put("8", 7)
res0: Option[Int] = None

scala> x foreach println
(8,7)

scala> x.toArray
res2: Array[(String, Int)] = Array((8,7))

why isn't it like toList?

toList: scala.List[(A, B)]
Was it helpful?

Solution

The scaladoc has all kinds of subtle bugs. The problem here is that you are seeing the "simplified" version of the method signature (meant as a way to convey the essential part of the signature and hide things such as CanBuildFrom in map/flatMap methods, which are really an implementation detail). The simplification got a little awry here, and does not seem to make much sense. If you click on the "full signature" link, you'll see that the real signature looks like:

def toArray[B >: (A, B)](implicit arg0: ClassTag[B]): Array[B]

In fact this is still wrong, as we certainly cannot have a type B where B >: (A, B). It should be more like :

def toArray[C >: (A, B)](implicit arg0: ClassTag[C]): Array[C]

The problem is that there are actually two Bs: the first one comes from the HashMap class declaration itself (HashMap[A, +B]) while the other one comes from the methods toArray defined in its base class TraversableOnce (def toArray[B >: A](implicit arg0: ClassTag[B]): Array[B]). It just happens that the scaladoc generator failed to dedup the two instances of B

OTHER TIPS

The API you see in the the Scaladoc of toArray:

def toArray[B >: (A, B)](implicit arg0: ClassTag[B]): Array[B]

Is equivalent to:

def toArray[C >: (A, B)](implicit arg0: ClassTag[C]): Array[C]

The choice of the type variable B is indeed unfortunate (and maybe even a Scaladoc bug, I'm not sure if you are allowed to write that).

It basically means you'll get an array of the most specific supertype of (A,B) for which a ClassTag is available. The ClassTag is required in order to create the Array.

This basically means that if at compile-time, the-run time type of the Map you are converting is fully known, you will get a Array[(A,B)]. However, if you have up-casted your Map somewhere, the run-time type of the resulting Array will depend on the up-casted type, and not on the runtime type. This is different behavior than toList and due to the JVMs restrictions on how native arrays can be created.

The Scaladoc is just wrong because it inherits toArray from TraversableOnce, where the type of the collection is A and the return value is B. The Array[A] thing is left over from TraversableOnce where A is whatever TraversableOnce is traversing (in this case, actually (A,B) for a different definition of A and B); and although it fills the (A,B) in properly in the long form, it still uses B as the new return variable instead of a different letter like C.

Kind of confusing! It actually should read

def toArray[C >: (A,B)](...[C]): Array[C]

and the short form should be

toArray: Array[(A,B)]

just like you expect.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top