自我类型和特质子类之间的区别是什么?

trait A的自我类型:

trait B
trait A { this: B => }

"__ABC0不能混合到不扩展B的具体类中"

另一方面,以下几点:

trait B
trait A extends B

A中混合的任何(具体或抽象)类也将在B中混合。

这两句话的意思难道不是一样的吗?自我类型似乎只用于产生简单的编译时错误的可能性。

我错过了什么?

72141 次浏览

在第一种情况下,B的一个子特征或子类可以混合到任何使用a的地方,所以B可以是一个抽象的特征。

self类型允许您指定允许在trait中混合哪些类型。例如,如果你有一个自我类型为Closeable的trait,那么该trait知道唯一允许混合它的东西必须实现Closeable接口。

Self类型允许您定义循环依赖关系。例如,你可以这样做:

trait A { self: B => }
trait B { self: A => }

使用extends的继承不允许这样。试一试:

trait A extends B
trait B extends A
error:  illegal cyclic reference involving trait A

在Odersky的书中,第33.5节(创建电子表格UI章节)提到:

在电子表格示例中,类Model继承自Evaluator和 从而获得其评价方法。同学们,走另一条路 Evaluator将自身类型定义为Model,如下所示:

package org.stairwaybook.scells
trait Evaluator { this: Model => ...

希望这能有所帮助。

它主要用于依赖注入,例如在饼模式中。在Scala中有一个伟大的文章覆盖了许多不同形式的依赖注入,包括饼模式。如果你谷歌“蛋糕模式和Scala”,你会得到很多链接,包括演示文稿和视频。现在,这里是另一个问题的链接。

现在,关于自我类型和扩展特质之间的区别,这很简单。如果你说B extends A,那么B 就是A。当你使用自类型时,B 需要A。使用自我类型创建了两个特定的需求:

  1. 如果B被扩展了,那么你是在要求中混合一个A
  2. 当一个具体类最终扩展/ mixed -in这些trait时,某些类/trait必须实现A

考虑以下例子:

scala> trait User { def name: String }
defined trait User


scala> trait Tweeter {
|   user: User =>
|   def tweet(msg: String) = println(s"$name: $msg")
| }
defined trait Tweeter


scala> trait Wrong extends Tweeter {
|   def noCanDo = name
| }
<console>:9: error: illegal inheritance;
self-type Wrong does not conform to Tweeter's selftype Tweeter with User
trait Wrong extends Tweeter {
^
<console>:10: error: not found: value name
def noCanDo = name
^

如果TweeterUser的子类,就不会有错误。在上面的代码中,每当使用Tweeter时,我们都会要求 User,但是Wrong没有提供User,因此我们得到了一个错误。现在,上面的代码仍然在范围内,考虑:

scala> trait DummyUser extends User {
|   override def name: String = "foo"
| }
defined trait DummyUser


scala> trait Right extends Tweeter with User {
|   val canDo = name
| }
defined trait Right


scala> trait RightAgain extends Tweeter with DummyUser {
|   val canDo = name
| }
defined trait RightAgain

使用Right,可以满足混合User的要求。然而,上面提到的第二个要求没有得到满足:实现User的负担仍然留给了扩展Right的类/特征。

RightAgain可以满足这两个要求。提供了UserUser的实现。

要了解更多实际用例,请参阅本回答开头的链接!希望你们现在明白了。

另一个区别是自类型可以指定非类类型。例如

trait Foo{
this: { def close:Unit} =>
...
}

这里的self类型是一个结构类型。其效果是说,任何在Foo中混合的东西都必须实现一个无参数的“close”方法返回单元。这为duck类型提供了安全的mixin。

让我们从周期依赖开始。

trait A {
selfA: B =>
def fa: Int }


trait B {
selfB: A =>
def fb: String }

然而,这个解决方案的模块化并不像它看起来那么好,因为你可以像这样重写自我类型:

trait A1 extends A {
selfA1: B =>
override def fb = "B's String" }
trait B1 extends B {
selfB1: A =>
override def fa = "A's String" }
val myObj = new A1 with B1

但是,如果重写了self类型的成员,就失去了对原始成员的访问权,仍然可以通过super using继承访问原始成员。因此,通过使用继承真正获得的是:

trait AB {
def fa: String
def fb: String }
trait A1 extends AB
{ override def fa = "A's String" }
trait B1 extends AB
{ override def fb = "B's String" }
val myObj = new A1 with B1

现在我不能说我已经理解了饼模式的所有微妙之处,但是我突然意识到,加强模块化的主要方法是通过组合而不是继承或自我类型。

继承版本更短,但我更喜欢继承而不是自我类型的主要原因是,我发现对自我类型进行正确的初始化顺序要棘手得多。然而,有些事情你可以用自我类型做,而不能用继承做。自我类型可以使用类型,而继承需要一个trait或类,如下所示:

trait Outer
{ type T1 }
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.

你甚至可以这样做:

trait TypeBuster
{ this: Int with String => }

尽管您永远无法实例化它。我没有看到任何绝对的理由不能够从一个类型继承,但我肯定认为它将有用的路径构造函数类和特征,因为我们有类型构造函数特征/类。是不幸的

trait InnerA extends Outer#Inner //Doesn't compile

我们有这个:

trait Outer
{ trait Inner }
trait OuterA extends Outer
{ trait InnerA extends Inner }
trait OuterB extends Outer
{ trait InnerB extends Inner }
trait OuterFinal extends OuterA with OuterB
{ val myV = new InnerA with InnerB }

或:

  trait Outer
{ trait Inner }
trait InnerA
{this: Outer#Inner =>}
trait InnerB
{this: Outer#Inner =>}
trait OuterFinal extends Outer
{ val myVal = new InnerA with InnerB with Inner }

我们应该更多地理解的一点是,特征可以扩展阶级。感谢David Maclver指出这一点。下面是我自己代码中的一个例子:

class ScnBase extends Frame
abstract class ScnVista[GT <: GeomBase[_ <: TypesD]](geomRI: GT) extends ScnBase with DescripHolder[GT] )
{ val geomR = geomRI }
trait EditScn[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
trait ScnVistaCyl[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]

ScnBase继承自摇摆不定的 Frame类,因此它可以用作self类型,然后在最后(实例化时)混合使用。然而,val geomR在被继承trait使用之前需要初始化。因此,我们需要一个类来强制geomR预先初始化。类ScnVista可以被多个正交特征继承,这些正交特征本身也可以被继承。使用多个类型参数(泛型)提供了另一种模块化形式。

更新:一个主要的区别是自类型可以依赖于多个类(我承认这是一个有点极端的情况)。例如,你可以有

class Person {
//...
def name: String = "...";
}


class Expense {
def cost: Int = 123;
}


trait Employee {
this: Person with Expense =>
// ...


def roomNo: Int;


def officeLabel: String = name + "/" + roomNo;
}

这允许将Employee mixin添加到任何PersonExpense的子类中。当然,这只有在Expense扩展Person时才有意义,反之亦然。关键是使用自类型Employee可以独立于它所依赖的类的层次结构。它不关心什么扩展什么——如果你切换了ExpensePerson的层次结构,你不必修改Employee

trait A { def x = 1 }
trait B extends A { override def x = super.x * 5 }
trait C1 extends B { override def x = 2 }
trait C2 extends A { this: B => override def x = 2}


// 1.
println((new C1 with B).x) // 2
println((new C2 with B).x) // 10


// 2.
trait X {
type SomeA <: A
trait Inner1 { this: SomeA => } // compiles ok
trait Inner2 extends SomeA {} // doesn't compile
}

Martin Odersky最初的Scala论文可伸缩组件抽象中的2.3节“Selftype注解”实际上很好地解释了自类型在mixin组合之外的用途:提供了一种将类与抽象类型关联起来的替代方法。

文中给出的例子如下所示,它似乎没有一个优雅的子类对应:

abstract class Graph {
type Node <: BaseNode;
class BaseNode {
self: Node =>
def connectWith(n: Node): Edge =
new Edge(self, n);
}
class Edge(from: Node, to: Node) {
def source() = from;
def target() = to;
}
}


class LabeledGraph extends Graph {
class Node(label: String) extends BaseNode {
def getLabel: String = label;
def self: Node = this;
}
}

其他答案总结:

  • 扩展的类型暴露给继承类型,但自类型不是

    class Cow { this: FourStomachs }允许你使用只适用于反刍动物的方法,比如digestGrass。扩展牛的特性将没有这样的特权。另一方面,class Cow extends FourStomachs会将digestGrass暴露给任何extends Cow的人。

  • 自我类型允许循环依赖,而扩展其他类型则不允许

另一件没有提到的事情是:因为自类型不是所需类的层次结构的一部分,它们可以从模式匹配中排除,特别是当您根据密封的层次结构进行穷尽匹配时。当你想要建模正交行为时,这很方便,比如:

sealed trait Person
trait Student extends Person
trait Teacher extends Person
trait Adult { this : Person => } // orthogonal to its condition


val p : Person = new Student {}
p match {
case s : Student => println("a student")
case t : Teacher => println("a teacher")
} // that's it we're exhaustive