When extending a trait within a trait, what does &

2019-04-04 02:08发布

问题:

I want to to extend a trait within a trait, like this:

  trait NodeTypes {
    trait Node {
      def allNodesHaveThis: Int
    }
  }

  trait ScrumptiousTypes extends NodeTypes {
    trait Node extends super.Node {
      def scrumptiousness: Int
    }
  }

  trait YummyTypes extends NodeTypes {
    trait Node extends super.Node {
      def yumminess: Int
    }
  }

  object Graph extends NodeTypes with ScrumptiousTypes with YummyTypes {
    case class Node() extends super.Node {
      override def allNodesHaveThis = 1
      override def scrumptiousness = 2  // error: ScrumptiousTypes.Node has been disinherited
      override def yumminess = 3
    }
  }

If this works, it would be a nice way of saying “When your Graph inherits from <Whatever>Types, its Node class must provide the methods required by <Whatever>.”

But the Scala 2.11.2 compiler says:

error: method scrumptiousness overrides nothing
      override def scrumptiousness = 2
                   ^

It appears that YummyTypes.Node shadows ScrumptiousTypes.Node, following the usual way that Scala resolves “diamond” inheritance for methods: by type linearization. As I understand things, that should be OK, though, because YummyTypes.Node explicitly extends super.Node, which, by the same type linearization, should refer to ScrumptiousTypes.

What have I misunderstood? Or, what does super.Node refer to—and why?


If you're wondering why I'm doing this, it's so I can mix changes into several traits at once, so the inherited traits interoperate, as explained in this question. In the final Node class (and other classes that it works with), I don't want to explicitly extend from each Node trait: I want to mix in from one "thing" (whatever it is) and get all the mutually consistent changes made to Node and the other traits, all in a bundle. Or, if one trait defines a bunch of extensions to Node, extending from ScrumptiousTypes should make all of the Node-extensions contain a scrumptiousness member, without having to list all the Node-extensions: trait Hypernode extends ScrumptiousTypes.Node, trait ZealousNode extends ScrumptiousTypes.Node, etc.

回答1:

use type also fix the issue

trait NodeTypes {

  trait Node {
    def allNodesHaveThis: Int
  }

}

trait ScrumptiousTypes extends NodeTypes {

  trait Node extends super.Node {
    def scrumptiousness: Int
  }

  type ScrumptiousTypesNode = this.Node
}

trait YummyTypes extends NodeTypes {

  trait Node extends super.Node {
    def yumminess: Int
  }

  type YummyTypesNode = this.Node
}

object Graph extends NodeTypes with ScrumptiousTypes with YummyTypes {

  case class Node() extends ScrumptiousTypesNode with YummyTypesNode {
    override def allNodesHaveThis = 1

    override def scrumptiousness = 2

    override def yumminess = 3
  }

}

------v2------- use object contain to Node , but since path depend it is not a good idea , and maybe It will be problems

trait NodeTypes {

  trait Node {
    def allNodesHaveThis: Int
  }

}

object NodeTypes extends NodeTypes

trait ScrumptiousTypes extends NodeTypes {

  trait Node {
    def scrumptiousness: Int
  }

  type ScrumptiousTypesNode = this.Node
}

object ScrumptiousTypes extends ScrumptiousTypes

trait YummyTypes extends NodeTypes {

  trait Node {
    def yumminess: Int
  }

  type YummyTypesNode = this.Node
}

object YummyTypes extends YummyTypes

trait Nodes {

  trait Nodes extends NodeTypes.Node with  YummyTypes.Node with ScrumptiousTypes.Node

}


object Graph extends  Nodes {

  case class Nodes() extends super.Nodes {
    override def yumminess: Int = 1
//
    override def scrumptiousness: Int = 2

    override def allNodesHaveThis: Int = 3
  }

}


回答2:

This is because of class lineraization. See Spec.

Explanation

Let C be a class with template C1 with ... with Cn. Then lineraization is concatenation of elements from Cn to C1, replacing all identical elements to left. Here elements include var, val, def, traits, object.

If you want to see the order of linearization, use

import scala.reflect.runtime.universe._
val tpe = typeOf[scala.collection.immutable.List[_]]
tpe.baseClasses foreach { s => println(s.fullName) }

In your case, if you change the order from ScrumptiousTypes with YummyTypes to YummyTypes with ScrumptiousTypes, then error will be on method yumminess.

An alternate to @余杰水 is to extend inner class like,

case class Node() extends super[ScrumptiousTypes].Node with super[YummyTypes].Node 


回答3:

This isn't meant to be an answer. It's just quotations from and interpretations of the spec, which are too long to fit readably into comments (prompted by johny's answer). I'm spelling out my interpretations so you might be able to spot where I went wrong. Maybe this will lead to an explanation or to a way to chain extensions of traits within traits (or to a bug report in the unlikely event that my interpretation turns out to be right).

Relevant passages from the Scala spec

§6.5 This and Super: A reference super.m refers statically to a method or type m in the least proper supertype of the innermost template containing the reference. It evaluates to the member m′ in the actual supertype of that template which is equal to m or which overrides m.

The big question is: What does the spec say that super.Node inside YummyTypes refers to? To find out, we'll need to know the definitions of the spec-specific terms used above:

§5.1 Templates: A template defines the type signature, behavior and initial state of a trait or class of objects or of a single object.

So, a template is what we'd ordinarily call an object, class, or trait definition.

§5.4 Traits: A trait is a class that is meant to be added to some other class as a mixin. … The least proper supertype of a template is the class type or compound type consisting of all its parent class types.

§5.1.2 Class Linearization: Let C be a class with template C1 with ... with Cn. The linearization of C, L(C), is defined as follows:

     L(C) = C,L(Cn) +⃗ … +⃗ L(C1)

Here +⃗ denotes concatenation where elements of the right operand replace identical elements of the left operand.

I take this to mean that the linearization is a sequence of classes, which you get by starting with the class being defined and then reading the with types from right to left. When two classes in the linearization define a member or type with the same name (an “element”), the class that comes first “wins”.

So, the linearization of Graph should be Graph,YummyTypes,ScrumptiousTypes,NodeTypes, followed by standard stuff like Any. Indeed, this is confirmed when I modify Graph like this:

  object Graph extends ScrumptiousTypes with YummyTypes {
    case class Node() extends super.Node { /* … */ }

    typeOf[Graph.type].baseClasses foreach { s => println(s.fullName) }
  }

which produces:

Graph
YummyTypes
ScrumptiousTypes
NodeTypes
java.lang.Object
scala.Any

§5.4 Traits: Assume a trait D defines some aspect of an instance x of type C (i.e. D is a base class of C). Then the actual supertype of D in x is the compound type consisting of all the base classes in L(C) that succeed D. The actual supertype gives the context for resolving a super reference in a trait. Note that the actual supertype depends on the type to which the trait is added in a mixin composition; it is not statically known at the time the trait is defined.

I take this to mean that the "actual" least proper supertype of a mixed-in trait is determined by the type of the actual object that the trait is mixed into (Graph in my example), not necessarily a supertype that the trait's definition explicitly extends (NodeTypes in my example).

Conclusion

So, it would appear that the actual supertype of YummyTypes in Graph should be ScrumptiousTypes. And so, the actual supertype of YummyTypes.Node in Graph should be ScrumptiousTypes.Node.

However, adding this line to Graph:

typeOf[Node].baseClasses foreach { s => println(s.fullName) }

produces:

Graph.Node
scala.Serializable
java.io.Serializable
scala.Product
scala.Equals
YummyTypes.Node
NodeTypes.Node
java.lang.Object
scala.Any

ScrumptiousTypes.Node is missing. Apparently, inside YummyTypes, super.Node does not refer to Node in YummyTypes' actual least proper supertype.

However, if I add:

abstract override def text = super.text + " ScrumptiousTypes" // in ScrumptiousTypes 
abstract override def text = super.text + " YummyTypes"       // in YummyTypes

printing text in Graph produces:

 ScrumptiousTypes YummyTypes

demonstrating that inside YummyTypes in Graph, super.text does refer to ScrumptiousTypes.text!