Continuing my series about strange source code.
Looking at Scala 2.12.12 scala.collection.TraversableOnce#reduceLeft#reducer I've found a very strange line:
def reduceLeft[B >: A](op: (B, A) => B): B = {
if (isEmpty)
throw new UnsupportedOperationException("empty.reduceLeft")
object reducer extends Function1[A, Unit] {
var first = true
var acc: B = 0.asInstanceOf[B] // <<<<===
override def apply(x: A): Unit =
if (first) {
acc = x
first = false
}
else acc = op(acc, x)
}
self foreach reducer
reducer.acc
}
What actually 0.asInstanceOf[B] does? Is it a workaround for making each type "nullable"?
For example, having
Seq("1", "2").reduceLeft(_ + _)
means the following code in the runtime
var acc: B = 0.asInstanceOf[String]
Why this cannot be simply replaced with var acc: B = null? Because it would require to introduce implicit ev: Null <:< A1 or what?
Update:
Moreover, simply casting Int to any other type throws an exception:
println(0.asInstanceOf[String])
Throws a runtime exception:
Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String
But why it doesn't throw an exception in case with reducer?
Update 2:
Diving deeper,
def foo[A]: A = 1.asInstanceOf[A]
println(foo[String]) // 1
println(foo[LocalDateTime]) // 1
println(foo[LocalDateTime].getClass) // java.lang.Integer
Source code
This is a partial answer.
You indeed cannot assign null to a value of type B that is not specified as B <: AnyRef without a cast. However, Scala generics are erased at runtime. The post-erasure post-boxing code would look something like that:
def reduceLeft(op: Function2): Object = {
if (isEmpty)
throw new UnsupportedOperationException("empty.reduceLeft")
object reducer extends Function1 {
var first = true
var acc: Object = BoxesRunTime.boxToInteger(0)
override def apply(x: Object): Unit =
if (first) {
acc = x
first = false
}
else acc = op(acc, x)
}
self foreach reducer
reducer.acc
}
Notice that the cast is gone too. There's zero information about what B was so there's no check to make.
Unlike there:
println(0.asInstanceOf[String])
This cast won't be erased, as String is a known type.
The erasure also explains why the foo calls work, as foo's erasure is basically:
def foo: Object = BoxesRunTime.boxToInteger(1)
Recall that println is defined as def println(any: Any): Unit, and post-scalac Any is replaced by Object. Therefore, when you're doing
println(foo[String]) // 1
println(foo[LocalDateTime]) // 1
the resulting 1 is never assigned to anything that would do a runtime class check. The object you get out is passed directly to println.
But this will cause a ClassCastException since the result of foo needs to be cast down to String for printString call
def printString(s: String) = println(s)
printString(foo[String])
and this will also fail at runtime:
val str = foo[String]
because Scala will infer the type of str to be String, and then runtime will fail to cast to that type.
Now, the part of an answer I don't have is why they didn't do var acc: B = _ (which only works inside classes/objects) or var acc: B = null.asInstanceOf[B] (which works everywhere you can define a var). It could probably just be a line that survived through a bunch of refactors.
In Scala 2.13 0.asInstanceOf[B] was changed to null.asInstanceOf[B] by Expression for all zero bits #8767
null.asInstanceOf[A]is more kosher than 0.It doesn't matter here because the value is never used and is always re-assigned.
It's too bad someone started accusing
var x: A = _of undue ugliness. That remains the best expression of non-assignment.
In Scala 3 we could write
trait Foo[B]:
var acc: B = compiletime.uninitialized
which IMO conveys intention clearly.
Related What is happening with 0.asInstanceOf[B] in Scala reduceLeft implementation
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With