web-dev-qa-db-ja.com

自己型と特性サブクラスの違いは何ですか?

特性の自己タイプA

trait B
trait A { this: B => }

"Aは、B"を拡張しない具体的なクラスに混在させることはできません。

一方、次のとおりです。

trait B
trait A extends B

"Aで混合する(コンクリートまたは抽象)クラスもB"で混合します

これら2つのステートメントは同じことを意味しないのですか?自己タイプは、単純なコンパイル時エラーの可能性を作成するためだけに役立つようです。

私は何が欠けていますか?

374
Dave

これは主に Dependency Injection に使用されます(ケーキパターンなど)。 すばらしい記事 が存在し、Cake Patternを含むScalaの多くの異なる形式の依存性注入をカバーしています。 「Cake Pattern and Scala」をGoogleで検索すると、プレゼンテーションやビデオなど、多くのリンクが表示されます。今のところ、 別の質問 へのリンクがあります。

さて、自己型と特性の拡張の違いは何ですか、それは簡単です。 B extends Aと言う場合、BisAです。自己型を使用する場合、BにはAが必要です。セルフタイプで作成される2つの特定の要件があります。

  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
                       ^

TweeterUserのサブクラスである場合、エラーは発生しません。上記のコードでは、Userが使用されるたびにTweeterrequiredしましたが、UserWrongに提供されなかったため、エラーが発生しました。さて、上記のコードがまだスコープ内にある状態で、以下を検討してください。

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を混在させる要件が満たされます。ただし、上記の2番目の要件は満たされていません。Userを拡張するクラス/特性には、Rightを実装する負担が残っています。

RightAgainを使用すると、両方の要件が満たされます。 UserUserの実装が提供されています。

より実用的な使用例については、この回答の冒頭にあるリンクをご覧ください!しかし、うまくいけば今それを取得します。

264

自己型を使用すると、周期的な依存関係を定義できます。たとえば、次のことを実現できます。

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

お役に立てれば。

153
Mushtaq Ahmed

もう1つの違いは、自己型が非クラス型を指定できることです。例えば

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

ここでの自己型は構造型です。その効果は、Fooで混合するものはすべて、ユニットを返す引数なしの「close」メソッドを実装する必要があるということです。これにより、カモタイピング用の安全なミックスインが可能になります。

56
Dave Griffith

Martin Oderskyの元のScala紙のセクション2.3「Selftype Annotations」 Scalable Component Abstractions は実際に目的を説明していますミックスイン構成をはるかに超える自己型の:クラスを抽象型に関連付ける代替方法を提供します。

論文で与えられた例は次のようなもので、エレガントなサブクラスの特派員がいないようです。

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

言及されていないもう1つのこと:自己型は必須クラスの階層の一部ではないため、特に封印された階層に対して徹底的に照合する場合、パターン照合から除外できます。これは、次のような直交動作をモデル化する場合に便利です。

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
12
Bruno Bieth

TL; DRのその他の回答の要約:

  • 拡張する型は継承型に公開されますが、自己型は公開されません

    例:class Cow { this: FourStomachs }は、反allows動物のみが利用できるdigestGrassなどのメソッドを使用できます。ただし、Cowを拡張する特性にはそのような特権はありません。一方、class Cow extends FourStomachsextends Cowの誰にでもdigestGrassを公開します。

  • 自己型は循環依存を許可しますが、他の型を拡張することはできません

10
jazmit

周期的な依存関係から始めましょう。

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

ただし、自己タイプのメンバーをオーバーライドすると、元のメンバーへのアクセスが失われます。元のメンバーは、継承を使用してスーパーを通じてアクセスできます。したがって、継承を使用することで得られるものは次のとおりです。

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型で実行できることがいくつかあります。自己型は型を使用できますが、継承には次のように特性またはクラスが必要です。

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 }

もっと共感すべき点の1つは、特性がクラスを拡張できることです。これを指摘してくれた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]

ScnBaseSwing Frameクラスを継承するため、自己型として使用し、最後に(インスタンス化時に)混合することができます。ただし、val geomRは、特性を継承して使用する前に初期化する必要があります。したがって、geomRの事前の初期化を強制するクラスが必要です。クラスScnVistaは、それ自体が継承できる複数の直交特性から継承できます。複数の型パラメーター(ジェネリック)を使用すると、代替形式のモジュール性が提供されます。

9
Rich Oliver
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
}
7
Oleg Galako

自己タイプを使用すると、特性をミックスインできるタイプを指定できます。たとえば、自己タイプCloseableを持つ特性がある場合、その特性は、それを混ぜることが許可されているものだけがCloseableインターフェースを実装する必要があることを知っています。

4
kikibobo

更新:主な違いは、セルフタイプはmultipleクラスに依存することができるということです(これは少しコーナーケースだと認めます)。たとえば、次のことができます

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およびPersonのサブクラスであるものだけにExpenseミックスインを追加できます。もちろん、これはExpensePersonを拡張する場合、またはその逆の場合にのみ意味があります。ポイントは、自己型Employeeを使用することは、依存するクラスの階層から独立している可能性があるということです。何が何を拡張するかは気にしません-ExpensePersonの階層を切り替えれば、Employeeを変更する必要はありません。

1
Petr Pudlák

前者の場合、Bのサブ特性またはサブクラスは、Aを使用するものに混ぜることができます。したがって、Bは抽象特性になります。

0
IttayD