自我类型和特质子类有什么区别?

时间:2010-01-02 08:07:54

标签: scala traits self-type

特质A的自我类型:

trait B
trait A { this: B => }

A不能混合到一个不会扩展B的具体类中。

另一方面,以下内容:

trait B
trait A extends B

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

这两个陈述不是一样的意思吗?自我类型似乎只是为了创建一个简单的编译时错误的可能性。

我错过了什么?

11 个答案:

答案 0 :(得分:260)

它主要用于Dependency Injection,例如蛋糕模式。存在great article,涵盖Scala中许多不同形式的依赖注入,包括Cake Pattern。如果您使用Google“Cake Pattern and Scala”,您将获得许多链接,包括演示文稿和视频。目前,这是指向another question的链接。

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

  1. 如果B已展开,则您必需混合使用A
  2. 当具体类最终扩展/混合这些特征时,某些类/特征必须实现A
  3. 请考虑以下示例:

    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的子类,则不会出现错误。在上面的代码中,每当User使用{<1}}时我们必需 Tweeter,但User未提供Wrong,所以我们得到了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扩展的类/特征中。

    User满足两个要求。提供了User和{{1}}的实现。

    有关更多实际用例,请参阅本答案开头的链接!但是,希望现在你明白了。

答案 1 :(得分:153)

自我类型允许您定义循环依赖项。例如,您可以实现此目的:

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 => ...

希望这有帮助。

答案 2 :(得分:56)

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

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

这里的自我类型是结构类型。结果是,在Foo中混合的任何东西都必须实现一个无法“关闭”的方法返回单位。这样可以安全地进行鸭子打字。

答案 3 :(得分:12)

Martin Odersky的原始Scala论文Scalable Component Abstractions的第2.3节“Selftype Annotations”实际上解释了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;
  }
}

答案 4 :(得分:12)

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

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

答案 5 :(得分:10)

TL;其他答案的DR摘要:

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

    例如:class Cow { this: FourStomachs }允许您使用仅适用于反刍动物的方法,例如digestGrass。然而,扩展Cow的特征将没有这样的特权。另一方面,class Cow extends FourStomachs会向digestGrass的任何人展示extends Cow

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

答案 6 :(得分:9)

让我们从周期性依赖开始。

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

虽然,如果覆盖自我类型的成员,则会失去对原始成员的访问权限,仍然可以通过super使用继承来访问该成员。那么使用继承真正获得的是:

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 Outer
{ type T1 }     
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.

你甚至可以这样做:

trait TypeBuster
{ this: Int with String => }

虽然你永远无法实例化它。我没有看到任何绝对的理由不能从类型继承,但我当然觉得有路径构造函数类和特性是有用的,因为我们有类型构造函数traits / classes。不幸的是

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继承自Swing Frame类,因此它可以用作自我类型,然后在最后混合(在实例化时)。但是,val geomR需要在继承特征之前初始化。因此,我们需要一个类来强制执行geomR的先前初始化。然后,类ScnVista可以从多个正交特征继承,这些特征本身可以从中继承。使用多个类型参数(泛型)提供了另一种模块化形式。

答案 7 :(得分:7)

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
}

答案 8 :(得分:4)

自我类型允许您指定允许混合特征的类型。例如,如果您有一个自我类型Closeable的特征,那么该特征知道允许混合它的唯一内容必须实现Closeable接口。

答案 9 :(得分:1)

更新:主要区别在于自我类型可能依赖于多个类(我承认这有点过时)。例如,你可以拥有

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可以独立于它所依赖的类的层次结构。它并不关心扩展内容的内容 - 如果您切换Expense vs Person的层次结构,则不必修改Employee

答案 10 :(得分:0)

在第一种情况下,B的子特征或子类可以混合到任何用途A.所以B可以是抽象特征。