ChatGPT解决这个技术问题 Extra ChatGPT

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

特征 A 的自我类型:

trait B
trait A { this: B => }

表示 "A 不能混入不扩展 B" 的具体类中。

另一方面,以下内容:

trait B
trait A extends B

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

这两个陈述不是同一个意思吗? self 类型似乎仅用于产生简单的编译时错误的可能性。

我错过了什么?

我实际上对自我类型和特征子类之间的差异感兴趣。我确实知道自我类型的一些常见用途。我只是找不到一个原因,为什么他们不会以同样的方式更清楚地使用子类型。
可以在自类型中使用类型参数:trait A[Self] {this: Self => } 是合法的,trait A[Self] extends Self 不是。
self 类型也可以是类,但 trait 不能从类继承。
@cvogt:特征可以从类继承(至少从 2.10 开始):pastebin.com/zShvr8LX
@Blaisorblade:但是,这不是可以通过重新设计小型语言来解决的问题,而不是基本限制吗? (至少从问题的角度来看)

C
Cory Klein

它主要用于 Dependency Injection,例如在蛋糕模式中。有一个 great article 涵盖了 Scala 中许多不同形式的依赖注入,包括 Cake Pattern。如果你用谷歌搜索“Cake Pattern and Scala”,你会得到很多链接,包括演示文稿和视频。目前,这里有一个指向 another question 的链接。

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

如果 B 被扩展,那么您需要混合 A。当具体类最终扩展/混合这些特征时,某些类/特征必须实现 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,但是 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 满足这两个要求。提供了 UserUser 的实现。

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


谢谢。蛋糕模式是我所说的 90% 的意思,为什么我谈论围绕自我类型的炒作……这是我第一次看到这个话题的地方。 Jonas Boner 的例子很棒,因为它强调了我的问题的重点。如果您将他的加热器示例中的自我类型更改为子特征,那么会有什么区别(除了如果您没有混合正确的东西,您在定义 ComponentRegistry 时得到的错误?
@Dave:你的意思是像 trait WarmerComponentImpl extends SensorDeviceComponent with OnOffDeviceComponent?这将导致 WarmerComponentImpl 拥有这些接口。它们可用于扩展 WarmerComponentImpl 的任何东西,这显然是错误的,因为它不是 SensorDeviceComponent,也不是 OnOffDeviceComponent。作为 self 类型,这些依赖项WarmerComponentImpl 可用。 List 可以用作 Array,反之亦然。但他们只是不一样的东西。
谢谢丹尼尔。这可能是我一直在寻找的主要区别。实际问题是,使用子类化会将您不希望的功能泄漏到您的界面中。这是违反更理论化的特征“is-part-of-a”规则的结果。自类型表示部件之间的“使用”关系。
@Rodney 不,不应该。事实上,将 this 与 self 类型一起使用是我看不起的事情,因为它无缘无故地遮蔽了原始 this
@opensas 试试 self: Dep1 with Dep2 =>
J
Jacek Laskowski

自类型允许您定义循环依赖关系。例如,您可以实现:

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

希望这可以帮助。


我没有考虑过这种情况。这是我见过的第一个例子,它与自我类型和子类不同。然而,这似乎有点小题大做,更重要的是,这似乎是个坏主意(我通常不去定义循环依赖!)。你觉得这是最重要的区别吗?
我认同。我看不出有任何其他原因为什么我更喜欢 self-types 而不是 extends 子句。自类型是冗长的,它们不会被继承(因此您必须将自类型添加到所有子类型作为一种仪式)并且您只能看到成员但不能覆盖它们。我很清楚 Cake 模式和许多提到 DI 的自我类型的帖子。但不知何故,我不相信。我很久以前就在这里创建了一个示例应用程序 (bitbucket.org/mushtaq/scala-di)。具体查看 /src/configs 文件夹。我实现了 DI 来替换没有自类型的复杂 Spring 配置。
Mushtaq,我们同意。我认为 Daniel 关于不公开无意功能的声明很重要,但正如您所说,这个“功能”有一个镜像视图......您不能覆盖该功能或在未来的子类中使用它。这很清楚地告诉我什么时候设计需要一个而不是另一个。在我发现真正需要之前,我将避免使用自我类型——即,如果我开始使用对象作为模块,正如 Daniel 指出的那样。我正在使用隐式参数和一个简单的引导程序对象自动装配依赖项。我喜欢简单。
@DanielC.Sobral 可能要感谢您的评论,但目前它比您的分析器拥有更多的赞成票。赞成两者:)
为什么不只创建一个特征 AB?由于特征 A 和 B 必须始终在任何最终类中组合,为什么首先将它们分开?
k
kostja

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

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

这里的self类型是结构类型。效果是说任何混入 Foo 的东西都必须实现一个无参数的“关闭”方法返回单元。这允许为鸭子打字提供安全的 mixin。


实际上,您也可以将继承与结构类型一起使用:abstract class A extends {def close:Unit}
我认为结构类型是使用反射,所以只有在没有其他选择时才使用......
@Adrian,我相信您的评论不正确。 ` abstract class A extends {def close:Unit}` 只是一个具有 Object 超类的抽象类。它只是 Scala 对无意义表达式的许可语法。你可以 ` class X extends { def f = 1 };新的 X().f` 例如
@Alexey我不明白为什么你的例子(或我的)是荒谬的。
@Adrian, abstract class A extends {def close:Unit} 相当于 abstract class A {def close:Unit} 。所以它不涉及结构类型。
B
Bruno Bieth

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

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

l
lcn

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;
  }
}

对于那些想知道为什么子类化不能解决这个问题的人,第 2.3 节还说:“mixin 组合 C_0 with ... with C_n 的每个操作数都必须引用一个类。 mixin 组合机制不允许任何 C_i 引用抽象类型。这种限制使得静态检查歧义并在类组合时覆盖冲突成为可能。”
C
Community

TL; DR 其他答案的摘要:

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

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


P
Peter Mortensen

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

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

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

然而,这个解决方案的模块化并不像它最初出现的那么好,因为您可以像这样覆盖 self 类型:

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 使用继承来访问它。因此,使用继承真正获得的是:

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

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

继承版本更短,但我更喜欢继承而不是 self 类型的主要原因是我发现使用 self 类型获得正确的初始化顺序更加棘手。然而,有些事情你可以用 self 类型做而你不能用继承做。自类型可以使用类型,而继承需要 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 继承自 Swing Frame 类,因此它可以用作 self 类型,然后在最后(在实例化时)混合。但是,val geomR 需要在通过继承特征使用之前进行初始化。所以我们需要一个类来强制执行 geomR 的事先初始化。然后类 ScnVista 可以被多个正交特征继承,这些特征本身可以被继承。使用多个类型参数(泛型)提供了另一种模块化形式。


O
Oleg Galako
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
}

B
Blaisorblade

self 类型允许您指定允许混合特征的类型。例如,如果您有一个自身类型为 Closeable 的 trait,那么该 trait 知道唯一允许混入它的东西必须实现 Closeable 接口。


@Blaisorblade:我想知道您是否误读了 kikibobo 的答案——特征的自我类型确实允许您限制可能将其混入的类型,这是其有用性的一部分。例如,如果我们定义 trait A { self:B => ... },那么声明 X with A 仅在 X 扩展 B 时才有效。是的,您可以说 X with A with Q,其中 Q 不扩展 B,但我相信 kikibobo 的观点是 X 是如此受约束.还是我错过了什么?
谢谢,你是对的。我的投票被锁定,但幸运的是我可以编辑答案然后更改我的投票。
P
Petr

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

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


Employee 不需要是从 Person 继承的类。特征可以扩展类。如果 Employee trait 扩展了 Person 而不是使用 self 类型,那么该示例仍然有效。我觉得你的例子很有趣,但它似乎没有说明自我类型的用例。
@MorganCreighton 足够公平,我不知道特征可以扩展类。如果我能找到更好的例子,我会考虑一下。
是的,这是一个令人惊讶的语言功能。如果 trait Employee 扩展了 Person 类,那么最终“包含”Employee 的任何类也必须扩展 Person。但是,如果 Employee 使用 self 类型而不是扩展 Person,那么这个限制仍然存在。干杯,彼得!
我不明白为什么“这只有在 Expense 扩展 Person 时才有意义,反之亦然。”
I
IttayD

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


不,B 在这两种情况下都可以(并且确实是)“抽象特征”。所以从这个角度来看没有区别。