模板初始化期间会发生什么

时间:2018-01-02 03:18:34

标签: scala traits

Scala official docs中,它说:

  

如果这是特征的模板,则其mixin-evaluation包含   对statsstats语句序列的评估。

     

如果这不是特质的模板,那么它的评估包括   以下步骤。

     

首先,评估超类构造函数sc。

     

然后,模板中的所有基类都会线性化直至模板   由sc表示的超类是混合评估的。 mixin的评价   在线性化中以相反的顺序发生。

     

最后,评估语句序列statsstats。

我想知道" mixin-evaluation"和"超类构造函数评估"这意味着什么为什么超类构造函数sc与特征mt1mt2mt3等区别对待?

1 个答案:

答案 0 :(得分:1)

嗯,这是一个复杂的事情,我不认为有一个很好的简短回答,除非你已经知道答案是什么。我认为简短的回答是,这是因为Scala被编译为JVM字节码,因此必须匹配该目标平台的限制。不幸的是,我不认为这个答案很明确,所以我的答案很长。

免责声明 :(对未来的读者来说是一种可耻的自我推销):如果你觉得这个很长的答案很有用,你也可以看一下利福的另一个问题my another long answer黄对类似的话题。

免责声明:提供翻译示例中的Java代码仅用于说明目的。他们的灵感来自于Scala编译器的实际功能,但并不匹配"真实的东西"在许多细节。此外,这些例子不能保证工作或编译。除非明确提到,否则我将使用(更简单的)代码示例,这些示例是Scala 2.12 / Java 8转换的简化版本,而不是旧的(和更复杂的)Scala / Java转换。

关于混合的一些理论

Mixin是面向对象设计中的一个想法,它有一些或多或少的封装逻辑,然而它本身并没有意义,所以它被添加到其他一些类中。这在某种意义上类似于multiple inheritance,多重继承实际上是Scala中设计此功能的方式。

如果您想在Scala中使用一些真实的混合示例,请参阅以下内容:

  1. Scala集合库实现基于混合。如果你看一下像scala.collection.immutable.List之类的定义,你会看到很多混合
  2. sealed abstract class List[+A] extends AbstractSeq[A]
                                  with LinearSeq[A]
                                  with Product // this one is not a mix-in!
                                  with GenericTraversableTemplate[A, List]
                                  with LinearSeqOptimized[A, List[A]] {
    

    在此示例中,mix-ins用于通过深层和广泛Scala集合层次结构中的核心方法共享高级方法的实现。

      用于依赖注入的
    1. Cake pattern基于混合,但这次混合逻辑通常根本没有意义。
    2. 重要的是,在Scala中,您可以混合使用逻辑(方法)和数据(字段)。

      关于Java / JVM和多重继承的一些理论

      "朴素"像C ++这样的语言所做的多重继承有一个臭名昭着的Diamond problem。为了解决这个问题,Java的原始设计并不支持任何逻辑或字段的多重继承。你可以"延伸"一个基类(完全继承其行为),另外你可以实现"许多接口意味着该类声称拥有来自接口的所有方法,但是您不能拥有从您的基接口继承的任何实际逻辑。 JVM中存在相同的限制。 20年后,在Java 8 Default Methods中添加了diamond problem。所以现在你可以继承一些方法,但仍然不能继承任何字段。这简化了Scala 2.12中混合的实现,其代价是要求Java 8作为其目标平台。静止接口不能具有(非静态)字段,因此不能具有构造函数。这是超类构造函数sc与特征mt1mt2mt3等区别对待的主要原因之一。

      另外值得注意的是,Java被设计为一种非常安全的语言。特别是它反对"未定义的行为"如果你重新使用一些只是内存中剩余(垃圾)的值,可能会发生这种情况。因此,Java确保您无法访问基类的任何字段,直到调用其构造函数为止。这使得super在任何子构造函数中调用了非常强制性的第一行。

      Scala和混合(简单示例)

      现在假设您是Scala语言的设计者,并且希望它具有混合,但您的目标平台(JVM)并不支持它们。你该怎么办?显然,您的编译器应该能够将混合内容转换为JVM支持的内容。以下是对简单(和无意义)示例如何完成的粗略近似:

      class Base(val baseValue: Int) {
      
      }
      
      trait TASimple {
        val aValueNI: AtomicInteger
        val aValueI: AtomicInteger = new AtomicInteger(0)
      
      
        def aIncrementAndGetNI(): Int = aValueNI.incrementAndGet()
      
        def aIncrementAndGetI(): Int = aValueI.incrementAndGet()
      }
      
      class SimpleChild(val childValue: Int, baseValue: Int) extends Base(baseValue) with TASimple {
        override val aValueNI = new AtomicInteger(5)
      }
      

      所以用言语表示:

      1. 带有一些字段的基类Base
      2. 混合特征TASimple,其中包含2个字段(一个初始化,一个未初始化)和两个方法
      3. 儿童班SimpleChild
      4. 由于TASimple不仅仅是方法声明,因此无法将其编译为简单的Java接口。它实际上编译成这样的东西(在Java代码中):

        public abstract interface TASimple
        {
          abstract void TASimple_setter_aValueI(AtomicInteger param);
        
          abstract AtomicInteger aValueNI();
        
          abstract AtomicInteger aValueI();
        
          default int aIncrementAndGetNI() { return aValueNI().incrementAndGet(); }
        
          default int aIncrementAndGetI() { return aValueI().incrementAndGet(); }
        
          public static void init(TASimple $this)
          {
            $this.TASimple_setter_aValueI(new AtomicInteger(0));
          }
        }
        
        
        public class SimpleChild extends Base implements TASimple
        {
          private final int childValue;
          private final AtomicInteger aValueNI;
          private final AtomicInteger aValueI;
        
          public AtomicInteger aValueI() { return this.aValueI; } 
          public void TASimple_setter_aValueI(AtomicInteger param) { this.aValueI = param; } 
          public int childValue() { return this.childValue; } 
          public AtomicInteger aValueNI() { return this.aValueNI; }
        
          public SimpleChild(int childValue, int baseValue)
          {
            super(baseValue); 
            TASimple.init(this);
            this.aValueNI = new AtomicInteger(5);
          }
        }
        

        那么TASimple包含什么以及它是如何被翻译的(到Java 8):

        1. aValueNIaValueI作为val声明的一部分。这些必须通过SimpleChild用一些字段(无任何技巧)支持它们来实现。

        2. aIncrementAndGetNIaIncrementAndGetI方法有一些逻辑。这些方法可以由SimpleChild继承,并将基于aValueNIaValueI方法。

        3. 初始化aValueI的逻辑。如果TASimple是一个类,它将有一个构造函数,这个逻辑可能就在那里。但是TASimple被转换为接口。因此,#34;构造函数"将一段逻辑移到static void init(TASimple $this)方法,并从init构造函数调用SimpleChild。请注意,Java规范强制要求在它之前调用super调用(即基类的构造函数)。

        4. 第3项中的逻辑是背后的原因

            

          首先,评估超类构造函数sc。   
            然后,模板的线性化中直到由sc表示的模板超类的所有基类都是混合评估的

          这也是JVM本身的逻辑强制执行:首先必须调用基础构造函数,然后才能(并且应该)调用所有其他模拟的#34;构造函数"所有混音。

          旁注(Scala pre-2.12 / Java pre-8)

          在Java 8和默认方法之前,翻译会更加复杂。 TASimple将被转换为接口和类,例如

          public abstract interface TASimple
          {
            public abstract void TASimple_setter_aValueI(AtomicInteger param);
          
            public abstract AtomicInteger aValueNI();
          
            public abstract AtomicInteger aValueI();
          
            public abstract int aIncrementAndGetNI();
          
            public abstract int aIncrementAndGetI();
          }
          
          public abstract class TASimpleImpl
          {
            public static int aIncrementAndGetNI(TASimple $this) { return $this.aValueNI().incrementAndGet(); }
            public static int aIncrementAndGetI(TASimple $this) { return $this.aValueI().incrementAndGet(); }
          
            public static void init(TASimple $this)
            {
              $this.TASimple_setter_aValueI(new AtomicInteger(0));
            }
          }
          
          
          public class SimpleChild extends Base implements TASimple
          {
            private final int childValue;
            private final AtomicInteger aValueNI;
            private final AtomicInteger aValueI;
          
            public AtomicInteger aValueI() { return this.aValueI; } 
            public void TASimple_setter_aValueI(AtomicInteger param) { this.aValueI = param; } 
            public int aIncrementAndGetNI() { return TASimpleImpl.aIncrementAndGetNI(this); } 
            public int aIncrementAndGetI() { return TASimpleImpl.aIncrementAndGetI(this); } 
            public int childValue() { return this.childValue; } 
            public AtomicInteger aValueNI() { return this.aValueNI; }
          
          
            public SimpleChild(int childValue, int baseValue)
            {
              super(baseValue); 
              TASimpleImpl.init(this);
              this.aValueNI = new AtomicInteger(5);
            }
          }
          

          注意现在如何将aIncrementAndGetNIaIncrementAndGetI的实现移动到一些将显式$this作为参数的静态方法。

          Scala和混合#2 (复杂的例子)

          上一节中的示例说明了一些想法,但并非全部。有关更详细的说明,则需要更复杂的示例。

            

          混合评估以线性化中出现的相反顺序发生。

          当您有多个混音时,尤其是{{3}}的情况下,此部分是相关的。请考虑以下示例:

          trait TA {
            val aValueNI0: AtomicInteger
            val aValueNI1: AtomicInteger
            val aValueNI2: AtomicInteger
            val aValueNI12: AtomicInteger
            val aValueI: AtomicInteger = new AtomicInteger(0)
          
          
            def aIncrementAndGetNI0(): Int = aValueNI0.incrementAndGet()
          
            def aIncrementAndGetNI1(): Int = aValueNI1.incrementAndGet()
          
            def aIncrementAndGetNI2(): Int = aValueNI2.incrementAndGet()
          
            def aIncrementAndGetNI12(): Int = aValueNI12.incrementAndGet()
          
            def aIncrementAndGetI(): Int = aValueI.incrementAndGet()
          }
          
          trait TB1 extends TA {
            val b1ValueNI: AtomicInteger
            val b1ValueI: AtomicInteger = new AtomicInteger(1)
          
            override val aValueNI1: AtomicInteger = new AtomicInteger(11)
            override val aValueNI12: AtomicInteger = new AtomicInteger(111)
          
            def b1IncrementAndGetNI(): Int = b1ValueNI.incrementAndGet()
          
            def b1IncrementAndGetI(): Int = b1ValueI.incrementAndGet()
          }
          
          trait TB2 extends TA {
            val b2ValueNI: AtomicInteger
            val b2ValueI: AtomicInteger = new AtomicInteger(2)
          
            override val aValueNI2: AtomicInteger = new AtomicInteger(22)
            override val aValueNI12: AtomicInteger = new AtomicInteger(222)
          
            def b2IncrementAndGetNI(): Int = b2ValueNI.incrementAndGet()
          
            def b2IncrementAndGetI(): Int = b2ValueI.incrementAndGet()
          }
          
          class Base(val baseValue: Int) {
          
          }
          
          
          class ComplicatedChild(val childValue: Int, baseValue: Int) extends Base(baseValue) with TB1 with TB2 {
            override val aValueNI0 = new AtomicInteger(5)
            override val b1ValueNI = new AtomicInteger(6)
            override val b2ValueNI = new AtomicInteger(7)
          }
          

          有趣的是,ComplicatedChild以两种方式从TA继承:TB1TB2。此外,TB1TB2都定义了aValueNI12的一些初始化,但具有不同的值。首先,应该提到ComplicatedChild只有val中定义的每个TA的一个字段副本。但是如果你试试这会发生什么:

          val cc = new inheritance.ComplicatedChild(42, 12345)
          println(cc.aIncrementAndGetNI12())
          

          哪个值(TB1TB2)会获胜?这种行为是否具有决定性?最后一个问题的答案是 - 是的,行为和编辑之间的行为将是确定性的。这是通过所谓的'特征线性化"来实现的。这是一个完全不同的话题。简而言之,Scala编译器以一些固定的定义顺序对所有继承的(直接和间接)特征进行排序,以便它表现出一些良好的行为(例如父特征总是在其列表中的子特征之后)。所以回到引用:

            

          混合评估以线性化中出现的相反顺序发生。

          此特征线性化顺序确保

          1. 所有" base"在调用某些特征的模拟构造函数时,相应的父(模拟)构造函数已经初始化了字段。

          2. 模拟构造函数调用的顺序是固定的,因此行为是确定性的。

          3. 在这种特殊情况下,线性化顺序为ComplicatedChild> TB2> TB1> TA> Base。这意味着ComplicatedChild构造函数实际上被翻译成:

              public ComplicatedChild(int childValue, int baseValue)
              {
                super(baseValue); 
                TA.init(this); 
                TB1.init(this); 
                TB2.init(this);
                this.aValueNI0 = new AtomicInteger(5);
                this.b1ValueNI = new AtomicInteger(6);
                this.b2ValueNI = new AtomicInteger(7);
              }
            

            所以aValueNI12将由TB2初始化(它将覆盖TB1"构造函数")设置的值。

            希望这能澄清一下发生了什么以及为什么。如果有什么不清楚,请告诉我。

            更新(回复评论)

              

            规范说

                 
                

            然后,模板的线性化中直到由scsc表示的模板超类的所有基类被混合评估。混合评估以线性化中出现的相反顺序发生。

              
                 

            最多 ”在这里的含义是什么?

            让我们扩展"简单"示例如下添加一个基础trait

            trait TX0 {
              val xValueI: AtomicInteger = new AtomicInteger(-1)
            }
            
            class Base(val baseValue: Int) extends TX0 {
            }
            
            trait TASimple extends TX0 {
              val aValueNI: AtomicInteger
              val aValueI: AtomicInteger = new AtomicInteger(0)
            
            
              def aIncrementAndGetNI(): Int = aValueNI.incrementAndGet()
            
              def aIncrementAndGetI(): Int = aValueI.incrementAndGet()
            }
            
            class SimpleChild(val childValue: Int, baseValue: Int) extends Base(baseValue) with TASimple {
              override val aValueNI = new AtomicInteger(5)
            }
            

            请注意TX0BaseClass继承了TASimple这里的内容。在这种情况下,我希望线性化产生以下顺序SimpleChild> TASimple> Base> TX0> Any。我将该句子解释如下:在这种情况下,SimpleChild的构造函数不会调用"模拟" TX0的构造函数,因为它出现在Base(= sc)之后的顺序中。我认为这种行为的逻辑是显而易见的:从SimpleChild构造函数的角度来看,"模拟" TX0的构造函数应该已经由Base构造函数调用,而且Base可能已更新该调用的结果,因此调用"模拟"第二次TX0的构造函数实际上可能会破坏Base