4
votes

I encountered this very peculiar behaviour that had me stumped for quite awhile. I've re-created it in a simple snippet below, code is lifted from Scala wrapper.

scala> def a = {
 | implicit val u = null
 | val x: Int = List(1,2,3).map(_.toString)
 | }
a: Unit

In the code above, there is no error thrown even though I know the type of val x to be List[String]. I can change the type of x to Int, Long, etc. and it will continue to compile fine.

However when I add explicitly state a type for the implicit val u, like in the example below, the compiler behaves as expected and throws an error.

scala> def a = {
 | implicit val u: Any = null
 | val x: Int = List(1,2,3).map(_.toString)
 | }
<console>:10: error: type mismatch;
 found   : List[String]
 required: Int

Has anyone else experienced this or has any insight into why this is happening?

2
A good reason not to use null and implicits without explicit types. - Michael Zajac
implicit val u = null says "I want to put an implicit value in scope for any type whatsoever and I want everything to blow up at runtime whenever that value is used". - Travis Brown
@TravisBrown it's also a handy home remedy for those pesky import language._ thingies. Might as well put those unused nulls to work. - som-snytt

2 Answers

9
votes

Due to how collections are designed in scala, null gets picked up as the implicit parameter of map, which has the following extended signature:

def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That

In this specific example, the implicit parameter must be a CanBuildFrom[List[Int], String, Int], and unfortunately null is a bottom type so it satisfies such requirement.

Check this QA for more details: How does CanBuildFrom know whether a type can build from another?

You can reproduce it without the implicit like this:

@ def a = {
    val x: Int = List(1,2,3).map(_.toString)(null)
  }
defined function a

Then, when you actually run the function, it tries to apply the builder but it's null, so... BOOM!

@ a
java.lang.NullPointerException
        scala.collection.TraversableLike$class.builder$1(TraversableLike.scala:240)
        scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
        scala.collection.immutable.List.map(List.scala:285)
        cmd3$.a(Main.scala:52)
        cmd4$$anonfun$1.apply$mcV$sp(Main.scala:52)
        cmd4$.<init>(Main.scala:53)
        cmd4$.<clinit>(Main.scala:-1)

you can see in the stacktrace that everything breaks as soon as the implementation of map on TraversableLike tries to access builder, which happens to be null.

4
votes

When you look at the signature of map you see that there is an implicit argument:

 def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That

When the compiler sees the implicit value inside the scope of call to map it seems to assume that it is supposed to be a CanBuildFrom[List[Int], String, Int]. So it expands to:

def a = {
  implicit val u: CanBuildFrom[List[Int], String, Int] = null
  val x: Int = List(1, 2, 3).map(_.toString)(u)
}

When you try to use it though, you can expect a NullPointerException.

Now, when you give u an explicit type the compiler can no longer find or infer a CanBuildFrom that would give the result type Int.