software - scala vs java
QuĆ© sucede con 0.asInstanceOf[B] en la implementaciĆ³n de Scala reduceLeft (2)
A continuación se muestra la fuente del método reduceLeft del rasgo TraversableOnce de Scala. ¿Qué sucede con la línea que lee var acc: B = 0.asInstanceOf[B]
?
Para mí, parece que si lo llamo en una lista de cadenas, como List("a", "b", "c")
, esto resultaría en algo como 0.asInstanceOf[String]
. Sin embargo, 0.asInstanceOf[String]
lanza una ClassCastException
en tiempo de ejecución si lo intento directamente.
¿Qué sucede con esa línea y por qué es diferente a llamar a 0.asInstanceOf[String]
directamente cuando se llama en una lista de 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
}
Pregunta extra: ¿por qué incluso se inicializa acc
a ese valor? Parece que el código en la primera iteración del bucle for
solo sobrescribirá ese valor con el primer elemento en el objeto TraversableOnce
.
Bueno, la razón por la que esto no falla con una ClassCastException
en tiempo de ejecución es porque:
- El compilador elimina el razonamiento de inicialización de que nunca se usa (lo dudo)
- B se erased a
Object
(oAnyRef
), por lo tanto, la0.asInstanceOf[Object]
es en realidad0.asInstanceOf[Object]
Puede probar la primera posibilidad comprobando el código de bytes, pero parece un fragmento de código sin sentido. Si no se elimina, entonces esto conlleva la sobrecarga innecesaria de encuadrar un Int
en cada llamada al método (aunque no es una creación de objeto, ver más abajo).
Averiguar lo que produce el compilador: 1
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
Y ahora:
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
}
Haz de eso lo que quieras!
Averiguar lo que produce el compilador: 2
Otra posibilidad es poner esto en un archivo fuente y compilarlo, imprimiendo la fase del código intermedio :
./scalac -Xprint:icode X.scala
entonces obtienes ...
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
};
¡Se ve claramente como el boxeo innecesario está ahí! Un punto es que esto no implicará la creación de un objeto, simplemente una búsqueda, porque los valores en caja de -127 a 127 se almacenan en caché.
Puede verificar la línea borrada cambiando la fase del compilador en el comando de impresión anterior a "borrar". Listo:
var acc: java.lang.Object = scala.Int.box(0).$asInstanceOf[java.lang.Object]();
La respuesta a la pregunta de bonificación se puede deducir al intentar proponer una alternativa. El acumulador es de tipo B. ¿Qué B le asignará? (Hay una mejor respuesta, concretamente null.asInstanceOf [B] que es lo que normalmente se hace, pero supongo que dejaría su pregunta en su lugar).