Below is the source for the reduceLeft method of Scala's TraversableOnce trait. What is happening with the line that reads var acc: B = 0.asInstanceOf[B]
?
To me, it seems that if I call this on a list of Strings, such as List("a", "b", "c")
, this would result in something like 0.asInstanceOf[String]
. However, 0.asInstanceOf[String]
throws a ClassCastException
at run time if I try it directly.
What is happening with that line, and why is it different than calling 0.asInstanceOf[String]
directly when called on a list of Strings?
def reduceLeft[B >: A](op: (B, A) => B): B = {
if (isEmpty)
throw new UnsupportedOperationException("empty.reduceLeft")
var first = true
var acc: B = 0.asInstanceOf[B]
for (x <- self) {
if (first) {
acc = x
first = false
}
else acc = op(acc, x)
}
acc
}
Bonus question: why is acc
even initialized to that value? It looks like the code in the first iteration of the for
loop will just overwrite that value with the first element in the TraversableOnce
object.
Type Casting in Scala is done using the asInstanceOf[] method. Applications of asInstanceof method. This perspective is required in manifesting beans from an application context file. It is also used to cast numeric types.
asInstanceOf[Bar] is a type cast, which is primarily a runtime operation. It says that the compiler should be coerced into believing that foo is a Bar . This may result in an error (a ClassCastException ) if and when foo is evaluated to be something other than a Bar at runtime.
Well, the reason this is not failing with a ClassCastException
at runtime is either because:
Object
(or AnyRef
), hence the cast is really 0.asInstanceOf[Object]
You could test the first possibility by checking the bytecode but it seems a pointless piece of code. If it is not elided, then this has the unnecessary overhead of boxing an Int
on every call to the method (although not an object creation - see below)!
Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_29).
Type in expressions to have them evaluated.
Type :help for more information.
scala> trait X[A] {
| def foreach[U](f: A => U): U
| def isEmpty = {
| var b = false
| foreach(_ => b = true)
| !b
| }
| def reduceLeft[B >: A](op: (B, A) => B): B = {
| if (isEmpty) sys.error("Bad")
| var first = true
| var acc: B = 0.asInstanceOf[B]
| for (x <- this) {
| if (first) { acc = x; first = false } else acc = op(acc, x)
| }
| acc
| }
| }
defined trait X
So now:
scala> :javap -v X
Compiled from "<console>"
public interface X extends scala.ScalaObject
SourceFile: "<console>"
Scala: length = 0x
Signature: length = 0x2
00 0D
InnerClass:
public abstract #19= #16 of #18; //X=class X of class
public final #21; //class X$$anonfun$isEmpty$1
public final #23; //class X$$anonfun$reduceLeft$1
minor version: 0
major version: 49
Constant pool:
const #1 = Asciz SourceFile;
const #2 = Asciz <console>;
const #3 = Asciz foreach;
const #4 = Asciz (Lscala/Function1;)Ljava/lang/Object;;
const #5 = Asciz <U:Ljava/lang/Object;>(Lscala/Function1<TA;TU;>;)TU;;
const #6 = Asciz Signature;
const #7 = Asciz isEmpty;
const #8 = Asciz ()Z;
const #9 = Asciz reduceLeft;
const #10 = Asciz (Lscala/Function2;)Ljava/lang/Object;;
const #11 = Asciz <B:Ljava/lang/Object;>(Lscala/Function2<TB;TA;TB;>;)TB;;
const #12 = Asciz Scala;
const #13 = Asciz <A:Ljava/lang/Object;>Ljava/lang/Object;Lscala/ScalaObject;;
const #14 = Asciz InnerClasses;
const #15 = Asciz X;
const #16 = class #15; // X
const #17 = Asciz ;
const #18 = class #17; //
const #19 = Asciz X;
const #20 = Asciz X$$anonfun$isEmpty$1;
const #21 = class #20; // X$$anonfun$isEmpty$1
const #22 = Asciz X$$anonfun$reduceLeft$1;
const #23 = class #22; // X$$anonfun$reduceLeft$1
const #24 = Asciz java/lang/Object;
const #25 = class #24; // java/lang/Object
const #26 = Asciz scala/ScalaObject;
const #27 = class #26; // scala/ScalaObject
{
public abstract java.lang.Object foreach(scala.Function1);
Signature: length = 0x2
00 05
public abstract boolean isEmpty();
public abstract java.lang.Object reduceLeft(scala.Function2);
Signature: length = 0x2
00 0B
}
Make of that what you will!
One other possibility is to put this in a source file and compile it, printing out the intermediate code phase:
./scalac -Xprint:icode X.scala
then you get...
def reduceLeft($this: X, op$1: Function2): java.lang.Object = {
if ($this.isEmpty())
scala.sys.`package`.error("Bad")
else
();
var first$1: scala.runtime.BooleanRef = new scala.runtime.BooleanRef(true);
var acc$1: scala.runtime.ObjectRef = new scala.runtime.ObjectRef(scala.Int.box(0));
$this.foreach({
(new anonymous class X$$anonfun$reduceLeft$1($this, op$1, first$1, acc$1): Function1)
});
acc.elem
};
It looks distinctly like the unnecessary boxing is in there! One point is that this won't involve an object creation, merely a lookup, because the boxed values of -127 to 127 are cached.
You can check the erased line by changing the compiler phase in the above print command to "erasure". Hey presto:
var acc: java.lang.Object = scala.Int.box(0).$asInstanceOf[java.lang.Object]();
The answer to the bonus question can be deduced by attempting to propose an alternative. The accumulator is of type B. What B will you assign to it? (There is a better answer, namely null.asInstanceOf[B] which is what is normally done, but I assume that would leave your question in place.)
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