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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1990948/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:49:03  来源:igfitidea点击:

What is the difference between self-types and trait subclasses?

scalatraitsself-type

提问by Dave

A self-type for a trait A:

trait 的 self 类型A

trait B
trait A { this: B => }

says that "Acannot be mixed into a concrete class that does not also extend B".

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

On the other hand, the following:

另一方面,以下内容:

trait B
trait A extends B

says that "any (concrete or abstract) class mixing in Awill also be mixing in B".

“混入的任何(具体的或抽象的)类A也将混入 B”

Don't these two statements mean the same thing? The self-type seems to serve only to create the possibility of a simple compile-time error.

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

What am I missing?

我错过了什么?

采纳答案by Daniel C. Sobral

It is predominately used for Dependency Injection, such as in the Cake Pattern. There exists a great articlecovering many different forms of dependency injection in Scala, including the Cake Pattern. If you Google "Cake Pattern and Scala", you'll get many links, including presentations and videos. For now, here is a link to another question.

它主要用于依赖注入,例如在蛋糕模式中。有一篇很棒的文章涵盖了 Scala 中许多不同形式的依赖注入,包括 Cake Pattern。如果您在 Google 上搜索“Cake Pattern and Scala”,您将获得许多链接,包括演示文稿和视频。现在,这里是另一个问题的链接。

Now, as to what is the difference between a self type and extending a trait, that is simple. If you say B extends A, then Bisan A. When you use self-types, Brequiresan A. There are two specific requirements that are created with self-types:

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

  1. If Bis extended, then you're requiredto mix-in an A.
  2. When a concrete class finally extends/mixes-in these traits, some class/trait must implement A.
  1. 如果B被扩展,那么你需要混合一个A.
  2. 当一个具体的类最终扩展/混合这些特征时,某些类/特征必须实现A.

Consider the following examples:

考虑以下示例:

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
                       ^

If Tweeterwas a subclass of User, there would be no error. In the code above, we requireda Userwhenever Tweeteris used, however a Userwasn't provided to Wrong, so we got an error. Now, with the code above still in scope, consider:

如果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

With Right, the requirement to mix-in a Useris satisfied. However, the second requirement mentioned above is not satisfied: the burden of implementing Userstill remains for classes/traits which extend Right.

对于Right,满足混合 aUser的要求。然而,上面提到的第二个要求没有得到满足:实现的负担User仍然存在于扩展的类/特征上Right

With RightAgainboth requirements are satisfied. A Userand an implementation of Userare provided.

有了RightAgain这两个要求都满足。提供了 AUser和一个实现User

For more practical use cases, please see the links at the start of this answer! But, hopefully now you get it.

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

回答by Mushtaq Ahmed

Self types allow you to define cyclical dependencies. For example, you can achieve this:

Self 类型允许您定义循环依赖项。例如,您可以实现这一点:

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

Inheritance using extendsdoes not allow that. Try:

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

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

In the Odersky book, look at section 33.5 (Creating spreadsheet UI chapter) where it mentions:

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

In the spreadsheet example, class Model inherits from Evaluator and thus gains access to its evaluation method. To go the other way, class Evaluator defines its self type to be Model, like this:

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

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

Hope this helps.

希望这可以帮助。

回答by Dave Griffith

One additional difference is that self-types can specify non-class types. For instance

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

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

The self type here is a structural type. The effect is to say that anything that mixes in Foo must implement a no-arg "close" method returning unit. This allows for safe mixins for duck-typing.

这里的 self 类型是结构类型。效果是说任何混合在 Foo 中的东西都必须实现一个无参数的“关闭”方法返回单元。这允许用于鸭子类型的安全混合。

回答by lcn

Section 2.3 "Selftype Annotations" of Martin Odersky's original Scala paper Scalable Component Abstractionsactually explains the purpose of selftype beyond mixin composition very well: provide an alternative way of associating a class with an abstract type.

Martin Odersky 的原始 Scala 论文Scalable Component Abstractions 的第 2.3 节“Selftype Annotations”实际上很好地解释了 selftype 超越 mixin 组合的目的:提供一种将类与抽象类型相关联的替代方法。

The example given in the paper was like the following, and it doesn't seem to have an elegant subclass correspondent:

论文中给出的例子是这样的,它似乎没有一个优雅的子类通讯员:

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

回答by Bruno Bieth

Another thing that has not been mentioned: because self-types aren't part of the hierarchy of the required class they can be excluded from pattern matching, especially when you are exhaustively matching against a sealed hierarchy. This is convenient when you want to model orthogonal behaviors such as:

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

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

回答by jazmit

TL;DR summary of the other answers:

TL; DR 对其他答案的总结:

  • Types you extend are exposed to inherited types, but self-types are not

    eg: class Cow { this: FourStomachs }allows you to use methods only available to ruminants, such as digestGrass. Traits that extend Cow however will have no such privileges. On the other hand, class Cow extends FourStomachswill expose digestGrassto anyone who extends Cow.

  • self-types allow cyclical dependencies, extending other types does not

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

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

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

回答by Rich Oliver

Let's start with the cyclical dependency.

让我们从循环依赖开始。

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

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

However, the modularity of this solution is not as great as it might first appear, because you can override self types as so:

然而,这个解决方案的模块化并不像它最初看起来的那么好,因为你可以像这样覆盖 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

Although, if you override a member of a self type, you lose access to the original member, which can still be accessed through super using inheritance. So what is really gained over using inheritance is:

但是,如果您覆盖 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

Now I can't claim to understand all the subtleties of the cake pattern, but it strikes me that the main method of enforcing modularity is through composition rather than inheritance or self types.

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

The inheritance version is shorter, but the main reason I prefer inheritance over self types is that I find it much more tricky to get the initialisation order correct with self types. However, there are some things you can do with self types that you can't do with inheritance. Self types can use a type while inheritance requires a trait or a class as in:

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

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

You can even do:

你甚至可以这样做:

trait TypeBuster
{ this: Int with String => }

Although you'll never be able to instantiate it. I don't see any absolute reason for not being be able to inherit from a type, but I certainly feel it would be useful to have path constructor classes and traits as we have type constructor traits / classes. As unfortunately

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

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

We have this:

我们有这个:

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 }

Or this:

或这个:

  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 }

One point that should be empathised more is that traits can extends classes. Thanks to David Maclver for pointing this out. Here's an example from my own code:

更应该感同身受的一点是,traits 可以扩展类。感谢 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]

ScnBaseinherits from the SwingFrame class, so it could be used as a self type and then mixed in at the end (at instantiation). However, val geomRneeds to be initialised before it's used by inheriting traits. So we need a class to enforce prior initialisation of geomR. The class ScnVistacan then be inherited from by multiple orthogonal traits which can themselves be inherited from. Using multiple type parameters (generics) offers an alternative form of modularity.

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

回答by 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
}

回答by kikibobo

A self type lets you specify what types are allowed to mixin a trait. For example, if you have a trait with a self type Closeable, then that trait knows that the only things that are allowed to mix it in, must implement the Closeableinterface.

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

回答by Petr Pudlák

Update:A principal difference is that self-types can depend on multipleclasses (I admit that's a bit corner case). For example, you can have

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

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

This allows to add the Employeemixin just to anything that is a subclass of Personand Expense. Of course, this is only meaningful if Expenseextends Personor vice versa. The point is that using self-types Employeecan be independent of the hierarchy of the classes it depends on. It doesn't care of what extends what - If you switch the hierarchy of Expensevs Person, you don't have to modify Employee.

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