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


387

特性の自己型A

trait B
trait A { this: B => }

言うAも伸びていない具体的なクラスに混入することはできませんB

一方、次のとおりです。

trait B
trait A extends B

その言う「任意の(コンクリートや抽象)クラスがでミキシングAもBに混合されます」

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

何が欠けていますか?


私は実際には、自己型と特性のサブクラス化の違いに興味があります。私は自己型の一般的な用途をいくつか知っています。彼らがサブタイピングと同じ方法でより明確に行われない理由を見つけることができません。
Dave

32
自己型内で型パラメーターを使用できます。これtrait A[Self] {this: Self => }は合法ですが、そうでtrait A[Self] extends Selfはありません。
Blaisorblade 2013年

3
自己型もクラスにすることができますが、特性はクラスから継承できません。
cvogt 2013年

10
@cvogt:トレイトはクラスから継承できます(少なくとも2.10以降):pastebin.com/zShvr8LX
Erik

1
@Blaisorblade:小さな言語の再設計で解決できるものではなく、基本的な制限ではありませんか?(少なくとも質問の観点から)
Erik Kaplun 2014年

回答:


273

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

さて、自己型と拡張特性の違いは何ですか?それは簡単です。あなたが言う場合はB extends A、その後B A。あなたは、自己のタイプを使用する場合、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
                       ^

TweeterサブクラスでUserある場合、エラーは発生しません。上記のコードでは、everyone を使用する必要がありましたが、a がに提供されていないため、エラーが発生しました。ここで、上記のコードがまだスコープ内にあるので、次のことを考慮してください。UserTweeterUserWrong

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

RightAgain両方の要件を満たしています。A Userとの実装Userされています。

より実際的な使用例については、この回答の冒頭にあるリンクを参照してください!しかし、うまくいけば、今あなたはそれを手に入れました。


3
ありがとう。ケーキ型は、私が自己型についての誇大宣伝について話す理由の90%です...それは私が最初にトピックを見た場所です。ジョナスボナーの例は、私の質問の要点を強調しているので素晴らしいです。彼のヒーターの例で自己タイプをサブトレイトに変更した場合、違いは何でしょうか(適切なものを混ぜない場合にComponentRegistryを定義するときに発生するエラー以外)
Dave

29
@デイブ:あなたはのような意味trait WarmerComponentImpl extends SensorDeviceComponent with OnOffDeviceComponentですか?それはWarmerComponentImplそれらのインターフェースを持つことになります。彼らは、拡張何に利用できるようになるWarmerComponentImplことがあるとして、明らかに間違っている、いないSensorDeviceComponent、もOnOffDeviceComponent。自己の種類としては、これらの依存関係が用意されています、排他的WarmerComponentImpl。AはListとして使用することができArray、その逆も同様です。しかし、それらは同じではありません。
ダニエルC.ソブラル2010年

10
ダニエルに感謝します。これはおそらく私が探していた大きな違いです。実際の問題は、サブクラス化を使用すると、意図しない機能がインターフェースにリークすることです。これは、特性のより理論的な「is-part-of-a」ルールの違反の結果です。自己型は、パーツ間の「用途」の関係を表します。
Dave

11
@Rodneyいいえ、そうすべきではありません。実際、this元のに理由がないので、自己型と一緒に使用することは私が軽視するものthisです。
ダニエルC.ソブラル2011年

9
@opensasお試しくださいself: Dep1 with Dep2 =>
ダニエルC.ソブラル

156

自己型を使用すると、循環依存関係を定義できます。たとえば、これを実現できます。

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

お役に立てれば。


3
私はこのシナリオを考慮していませんでした。私が見た最初の例は、サブクラスの場合の自己型とは異なります。しかしながら、それは一種のエッジケーシーであり、さらに重要なことに、それは悪い考えのように見えます(私は通常、循環的な依存関係を定義しないように私の方法から遠く離れています!)これが最も重要な違いだと思いますか?
Dave

4
私はそう思う。私が節を拡張するよりも自己型を好む理由は他にありません。自己型は冗長であり、継承されないため(すべてのサブタイプに儀式として自己型を追加する必要があります)、メンバーのみを表示できますが、オーバーライドすることはできません。私は、Cakeパターンと、DIの自己型に言及する多くの投稿をよく知っています。しかし、どういうわけか私は確信していません。ここでサンプルアプリを作成しました(bitbucket.org/mushtaq/scala-di)。特に/ src / configsフォルダーを確認してください。自己型なしで複雑なSpring構成を置き換えるDIを達成しました。
Mushtaq Ahmed

ムシュタク、私たちは同意しています。意図しない機能を公開しないというダニエルの発言は重要だと思いますが、この「機能」のミラービューがあります。機能をオーバーライドしたり、将来のサブクラスで使用したりすることはできません。これは、デザインがどちらを必要とするかをかなりはっきりと教えてくれます。真の必要性が見つかるまで、つまり、ダニエルが指摘するようにオブジェクトをモジュールとして使用し始めるまで、自己型付けを避けます。暗黙的なパラメーターと単純なブートストラップオブジェクトを使用して依存関係を自動配線しています。私はシンプルさが好きです。
Dave

@ DanielC.Sobralはあなたのコメントのおかげかもしれませんが、現時点ではあなたの回答よりも賛成票が多くなっています。両方の賛成:)
rintcius 2012

なぜ1つの特性ABを作成しないのですか?特性AとBは常に最終クラスで組み合わせる必要があるので、なぜ最初にそれらを分離するのですか?
リッチオリバー

56

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

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

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


41
実際、構造型でも継承を使用できます。抽象クラスAは{def close:Unit}を拡張します
Adrian

12
構造化タイピングはリフレクションを使用していると思うので、他に選択肢がない場合にのみ使用してください...
Eran Medan 2013

@エイドリアン、あなたのコメントは間違っていると思います。`抽象クラスAは{def close:Unit}を拡張します`はObjectスーパークラスを持つ抽象クラスです。それは、無意味な式に対するScalaの許容構文にすぎません。`class X extends {def f = 1}; 新しいX()。f`の例
Alexey

1
@Alexeyなぜあなたの例(または私のもの)が無意味なのかわかりません。
エイドリアン

1
@Adrianはとabstract class A extends {def close:Unit}同等abstract class A {def close:Unit}です。したがって、構造タイプは含まれません。
Alexey

13

Martin OderskyのオリジナルScalaペーパーScalable Component Abstractionsのセクション2.3「Selftype Annotations」は、実際にはミックスイン構成を超えたセルフタイプの目的を非常によく説明しています。抽象型にクラスを関連付ける別の方法を提供してください。

論文で与えられた例は次のようなものであり、エレガントなサブクラスの対応物を持っていないようです:

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は次のようにも述べています。ミックスイン構成メカニズムでは、C_iが抽象型を参照することはできません。この制限により、あいまいさを静的にチェックし、クラスが構成される時点で競合をオーバーライドすることができます。」
ルーク・モーラー2017

12

言及されていないもう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

10

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

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

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

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


9

循環依存関係から始めましょう。

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

今、私はケーキのパターンのすべての微妙な点を理解していると主張することはできませんが、モジュール性を強制する主な方法は、継承や自己型ではなく構成を介することです。

継承バージョンは短いですが、自己型よりも継承を好む主な理由は、自己型を使用して初期化順序を正しく取得するのがはるかに難しいことです。ただし、継承では実行できないセルフタイプで実行できるいくつかの処理があります。自己型は型を使用できますが、継承では次のように特性またはクラスが必要です。

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]

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


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
}

4

自己型を使用すると、特性で混合できる型を指定できます。たとえば、セルフタイプの特性がある場合Closeable、その特性は、それを混在させることができるものだけがCloseableインターフェースを実装する必要があることを認識しています。


3
@Blaisorblade:キキボボの答えを間違って読んでいないのではないかと思います-特性の自己型は確かに、それを混合する可能性のある型を制約することを可能にし、それはその有用性の一部です。たとえば、定義するとtrait A { self:B => ... }X with AXがBを拡張する場合にのみ宣言が有効になります。はい、X with A with QQはBを拡張しない場合にと言うことができますが、キキボボのポイントはXが非常に制約されているということです。または私は何かを逃したのですか?
AmigoNico 2013年

1
ありがとう、あなたは正しい。投票はロックされましたが、幸いにも回答を編集して投票を変更することができました。
Blaisorblade 2013年

1

更新:主な違いは、自己型は複数のクラスに依存する可能性があることです(私はそれが少しコーナーケースであることを認めます)。たとえば、

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

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


従業員は、Personから派生するクラスである必要はありません。トレイトはクラスを拡張できます。従業員特性が自己型を使用する代わりにPersonを拡張した場合でも、例は機能します。あなたの例は興味深いと思いますが、自己型の使用例を示しているようには見えません。
モーガンクレイトン、2012年

@MorganCreighton公平に言えば、トレイトがクラスを拡張できることは知りませんでした。より良い例が見つかれば、それについて考えます。
PetrPudlák2012年

はい、それは驚くべき言語機能です。特性EmployeeがクラスPersonを拡張した場合、最終的にEmployeeが「内部にある」クラスはPersonも拡張する必要があります。しかし、EmployeeがPersonを拡張する代わりにselfタイプを使用した場合でも、その制限は存在します。乾杯、ペトル!
モーガンクレイトン

1
「費用が人を拡張する場合、またはその逆の場合にのみ意味がある」理由はわかりません。
Robin Green

0

最初のケースでは、BのサブトレイトまたはサブクラスをAを使用するものに混在させることができます。したがって、Bは抽象的なトレイトにすることができます。


いいえ、どちらの場合でも、Bは「抽象的な特性」である可能性があります(実際、そうです)。したがって、その観点からの違いはありません。
ロビングリーン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.