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

Dav*_*ave 377 scala traits self-type

特质的自我类型A:

trait B
trait A { this: B => }
Run Code Online (Sandbox Code Playgroud)

" A不能混合成一个不会扩展的具体类B".

另一方面,以下内容:

trait B
trait A extends B
Run Code Online (Sandbox Code Playgroud)

"混合的任何(混凝土或抽象)类A也将在B中混合".

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

我错过了什么?

Dan*_*ral 264

它主要用于依赖注入,例如Cake Pattern.在Scala中存在一篇很好的文章,涵盖了许多不同形式的依赖注入,包括Cake Pattern.如果您使用Google"Cake Pattern and Scala",您将获得许多链接,包括演示文稿和视频.目前,这是另一个问题的链接.

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

  1. 如果B延长,那么你需要混合一个A.
  2. 当具体类最终扩展/混合这些特征时,某些类/特征必须实现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
                       ^
Run Code Online (Sandbox Code Playgroud)

如果Tweeter是子类User,则不会有错误.在上面的代码中,我们需要一个User无论何时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
Run Code Online (Sandbox Code Playgroud)

因此Right,满足混合的要求User.但是,上述第二个要求并不令人满意:实施的负担User仍然存在于延伸的类别/特征中Right.

有了RightAgain这两个要求得到满足.提供了A User和实现User.

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

  • @Dave:你的意思是`trait WarmerComponentImpl使用OnOffDeviceComponent扩展SensorDeviceComponent?这将导致`WarmerComponentImpl`拥有这些接口.它们可用于扩展`WarmerComponentImpl`的任何东西,这显然是错误的,因为它不是`SensorDeviceComponent`,也不是`OnOffDeviceComponent`.作为一个自我类型,这些依赖项可以_exclusively_到`WarmerComponentImpl`.`List`可以用作`Array`,反之亦然.但他们不是一回事. (29认同)
  • @Rodney不,不应该.事实上,使用自我类型的`this`是我看不起的东西,因为它没有任何理由使原始的`this`成为阴影. (11认同)
  • 谢谢Daniel.这可能是我寻找的主要区别.实际问题是使用子类会将功能泄漏到您不想要的界面中.这是因为违反了更为理论的"属于某一部分"的特征规则.自我类型表达了部分之间的"使用 - "关系. (10认同)
  • @opensas尝试`self:Dep1 with Dep2 =>`. (9认同)
  • 谢谢.蛋糕模式是我的意思的90%,为什么我谈论围绕自我类型的炒作......这是我第一次看到这个主题的地方.Jonas Boner的例子非常棒,因为它强调了我的问题.如果你将他的加热器示例中的自我类型更改为subtraits那么会有什么区别(除了你在定义ComponentRegistry时得到的错误,如果你没有混合正确的东西? (3认同)

Mus*_*med 153

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

trait A { self: B => }
trait B { self: A => }
Run Code Online (Sandbox Code Playgroud)

继承使用extends不允许这样做.尝试:

trait A extends B
trait B extends A
error:  illegal cyclic reference involving trait A
Run Code Online (Sandbox Code Playgroud)

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

在电子表格示例中,类Model继承自Evaluator,因此可以访问其评估方法.换句话说,类Evaluator将其自身类型定义为Model,如下所示:

package org.stairwaybook.scells
trait Evaluator { this: Model => ...
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助.

  • 我认同.我没有看到任何其他原因我更喜欢自我类型扩展条款.自我类型是冗长的,它们不会被继承(所以你必须将自我类型作为一种仪式添加到所有子类型),你只能看到成员但不能覆盖它们.我很清楚蛋糕模式和许多提到DI的自我类型的帖子.但不知怎的,我不相信.我早就在这里创建了一个示例应用程序(http://bitbucket.org/mushtaq/scala-di/).具体来看/ src/configs文件夹.我实现了DI来替换没有自我类型的复杂Spring配置. (4认同)
  • 我没有考虑过这种情况.它是我见过的第一个与自我类型不同的东西,就像它与子类一样.然而,它似乎有点边缘,更重要的是,它似乎是一个坏主意(我通常远远不会定义循环依赖!).你觉得这是最重要的区别吗? (3认同)

Dav*_*ith 56

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

trait Foo{
   this: { def close:Unit} => 
   ...
}
Run Code Online (Sandbox Code Playgroud)

这里的自我类型是结构类型.结果是,在Foo中混合的任何东西都必须实现一个无法"关闭"的方法返回单元.这允许安全混合用于鸭子打字.

  • 实际上你也可以使用结构类型的继承:抽象类A扩展{def close:Unit} (41认同)
  • 我认为结构类型是使用反射,所以只有在没有其他选择时才使用... (12认同)

lcn*_*lcn 13

第2.3节"自我类型注释"Martin Odersky的原始Scala论文可伸缩组件抽象实际上解释了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;
  }
}
Run Code Online (Sandbox Code Playgroud)


Bru*_*eth 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
Run Code Online (Sandbox Code Playgroud)


jaz*_*mit 10

TL; DR摘要其他答案:

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

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

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


Ric*_*ver 9

让我们从周期性依赖开始吧.

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

trait B {
  selfB: A =>
  def fb: String }
Run Code Online (Sandbox Code Playgroud)

但是,此解决方案的模块化并不像它首次出现的那么大,因为您可以覆盖自我类型:

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
Run Code Online (Sandbox Code Playgroud)

但是,如果覆盖自身类型的成员,则会失去对原始成员的访问权限,但仍可通过超级继承访问该成员.那么使用继承真正获得的是:

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
Run Code Online (Sandbox Code Playgroud)

现在我无法理解蛋糕模式的所有微妙之处,但令我印象深刻的是,强制模块化的主要方法是通过组合而不是继承或自我类型.

继承版本较短,但我更喜欢继承而不是自我类型的主要原因是我发现使用自我类型使初始化顺序正确更加棘手.但是,您可以使用继承无法执行的自我类型.自我类型可以使用类型,而继承需要特征或类,如:

trait Outer
{ type T1 }     
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.
Run Code Online (Sandbox Code Playgroud)

你甚至可以这样做:

trait TypeBuster
{ this: Int with String => }
Run Code Online (Sandbox Code Playgroud)

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

trait InnerA extends Outer#Inner //Doesn't compile
Run Code Online (Sandbox Code Playgroud)

我们有这个:

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 }
Run Code Online (Sandbox Code Playgroud)

或这个:

  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 }
Run Code Online (Sandbox Code Playgroud)

应该更加强调的一点是,特征可以扩展类.感谢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]
Run Code Online (Sandbox Code Playgroud)

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


Ole*_*ako 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
}
Run Code Online (Sandbox Code Playgroud)