特征 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
不是。
它主要用于 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
^
如果 Tweeter
是 User
的子类,则不会出现错误。在上面的代码中,每当使用 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
满足这两个要求。提供了 User
和 User
的实现。
有关更多实际用例,请参阅此答案开头的链接!但是,希望现在你明白了。
自类型允许您定义循环依赖关系。例如,您可以实现:
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 => ...
希望这可以帮助。
另一个区别是自类型可以指定非类类型。例如
trait Foo{
this: { def close:Unit} =>
...
}
这里的self类型是结构类型。效果是说任何混入 Foo 的东西都必须实现一个无参数的“关闭”方法返回单元。这允许为鸭子打字提供安全的 mixin。
abstract class A extends {def close:Unit}
相当于 abstract class A {def close:Unit}
。所以它不涉及结构类型。
另一件没有提到的事情:因为自类型不是所需类的层次结构的一部分,它们可以从模式匹配中排除,特别是当您对密封的层次结构进行详尽匹配时。当您想要对正交行为进行建模时,这很方便,例如:
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
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;
}
}
TL; DR 其他答案的摘要:
您扩展的类型暴露给继承的类型,但自类型不是例如:class Cow { this: FourStomachs } 允许您使用仅适用于反刍动物的方法,例如 digestGrass。然而,扩展 Cow 的特征将没有这样的特权。另一方面,Cow 类扩展 FourStomachs 会将 digestGrass 暴露给扩展 Cow 的任何人。
自类型允许循环依赖,扩展其他类型不允许
让我们从周期性依赖开始。
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
可以被多个正交特征继承,这些特征本身可以被继承。使用多个类型参数(泛型)提供了另一种模块化形式。
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
}
self 类型允许您指定允许混合特征的类型。例如,如果您有一个自身类型为 Closeable
的 trait,那么该 trait 知道唯一允许混入它的东西必须实现 Closeable
接口。
trait A { self:B => ... }
,那么声明 X with A
仅在 X 扩展 B 时才有效。是的,您可以说 X with A with Q
,其中 Q 不扩展 B,但我相信 kikibobo 的观点是 X 是如此受约束.还是我错过了什么?
更新:一个主要的区别是自我类型可以依赖于多个类(我承认这有点极端)。例如,您可以拥有
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 添加到任何 Person
和 Expense
的子类中。当然,这仅在 Expense
扩展 Person
时才有意义,反之亦然。关键是使用自类型 Employee
可以独立于它所依赖的类的层次结构。它不关心什么扩展什么 - 如果您切换 Expense
与 Person
的层次结构,您不必修改 Employee
。
在第一种情况下,B 的子特征或子类可以混合到任何用途 A 中。因此 B 可以是抽象特征。
不定期副业成功案例分享
trait WarmerComponentImpl extends SensorDeviceComponent with OnOffDeviceComponent
?这将导致WarmerComponentImpl
拥有这些接口。它们可用于扩展WarmerComponentImpl
的任何东西,这显然是错误的,因为它不是SensorDeviceComponent
,也不是OnOffDeviceComponent
。作为 self 类型,这些依赖项仅对WarmerComponentImpl
可用。List
可以用作Array
,反之亦然。但他们只是不一样的东西。this
与 self 类型一起使用是我看不起的事情,因为它无缘无故地遮蔽了原始this
。self: Dep1 with Dep2 =>
。