【问题标题】:What is happening with 0.asInstanceOf[B] in Scala reduceLeft implementationScala reduceLeft 实现中的 0.asInstanceOf[B] 发生了什么
【发布时间】:2012-01-17 22:02:03
【问题描述】:

以下是 Scala 的 TraversableOnce 特征的 reduceLeft 方法的源代码。读取 var acc: B = 0.asInstanceOf[B] 的行发生了什么?

对我来说,如果我在字符串列表上调用它,例如List("a", "b", "c"),这将导致类似0.asInstanceOf[String] 的结果。但是,如果我直接尝试,0.asInstanceOf[String] 在运行时会抛出 ClassCastException

该行发生了什么,为什么它与在字符串列表上调用时直接调用 0.asInstanceOf[String] 不同?

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
}

额外问题:为什么acc 甚至被初始化为那个值?看起来for 循环的第一次迭代中的代码只会用TraversableOnce 对象中的第一个元素覆盖该值。

【问题讨论】:

    标签: scala


    【解决方案1】:

    好吧,在运行时ClassCastException 没有失败的原因是:

    1. 编译器删除了从未使用过的初始化推理(我对此表示怀疑)
    2. B 是 erasedObject(或 AnyRef),因此演员表实际上是 0.asInstanceOf[Object]

    您可以通过检查字节码来测试第一种可能性,但这似乎是一段毫无意义的代码。如果它没有被忽略,那么在每次调用该方法时都会产生不必要的装箱 Int 开销(尽管不是对象创建 - 见下文)!

    弄清楚编译器产生了什么: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
    

    那么现在:

    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 
    
    }
    

    随心所欲!

    弄清楚编译器产生了什么:2

    另一种可能性是将其放入源文件和compile it, printing out the intermediate code phase

    ./scalac -Xprint:icode X.scala
    

    然后你得到...

    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
    };
    

    看起来很明显那里有不必要的拳击!有一点是这不会涉及对象创建,仅涉及查找,因为 -127 到 127 的装箱值被缓存。

    您可以通过将上述打印命令中的编译器阶段更改为“擦除”来检查已擦除的行。嘿,快点:

    var acc: java.lang.Object = scala.Int.box(0).$asInstanceOf[java.lang.Object]();
    

    【讨论】:

    • 您检查编译器是否删除了初始化? -Xprint:typer 可能建议使用 scala.Int.box(0) 对其进行初始化。但是我不知道在字节码生成时会发生什么。
    • 确实 - 我还没有检查。我怀疑这条线没有被省略
    • 我不能说字节码启发了我,但./scalac -Xprint:icode X.scala 输出非常有帮助,同时您确认它显示了不必要的装箱。谢谢;当我将来遇到问题时,我会记得那个编译器切换!
    • 我发现要查看的 3 个有用阶段是 icodetypererasure
    • 我对 Java 中的类型擦除非常熟悉,以至于我有点羞愧,因为我没有想到 B 会被类型擦除。我想我忽略了这一点,因为我认为它必须做一些有用的事情。骄傲是为有用的提示付出的小代价。谢谢你教我钓鱼而不是给我一条鱼。
    【解决方案2】:

    可以通过尝试提出替代方案来推断奖励问题的答案。累加器的类型是 B。你将分配给它什么 B? (有一个更好的答案,即 null.asInstanceOf[B] 这是通常所做的,但我认为这会让你的问题留在原地。)

    【讨论】:

    • +1 用于回答额外问题并提供更好的选择。为了回应“但我认为这会让你的问题留在原地”,如果代码是null.asInstanceOf[B],我实际上永远不会问这个问题。最初让我失望的是认为 0 以某种方式被强制转换为任意类型
    • 只能通过这个实现 - 该方法可以简单地实现为(self.foldLeft(None: Option[B])(op)) getOrElse throw new UOE()
    • ...我想知道进行了哪些分析来确定当前实现的性能更高(以及性能提高了多少)?
    • 我的意思当然是:(self.foldLeft(None: Option[B]) { (ob, a) =&gt; ob map (b =&gt; op(b, a)) orElse Some(a: B) } getOrElse throw new UOE。我猜在非空的情况下,这涉及到相当多的对象创建。
    • 如果我们要为 reduceLeft(选项,真的吗?)提供一个优雅但性能不佳的实现,那么跳过它并让每个人都根据折叠来编写会更有意义。这些集合有这些看起来很奇怪的实现,所以你不必这样做。
    猜你喜欢
    • 2021-07-03
    • 1970-01-01
    • 1970-01-01
    • 2018-11-22
    • 2011-03-25
    • 2018-11-12
    • 2018-06-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多