特性の自己タイプA
:
trait B
trait A { this: B => }
"A
は、B
"を拡張しない具体的なクラスに混在させることはできません。
一方、次のとおりです。
trait B
trait A extends B
"A
で混合する(コンクリートまたは抽象)クラスもB"で混合します。
これら2つのステートメントは同じことを意味しないのですか?自己タイプは、単純なコンパイル時エラーの可能性を作成するためだけに役立つようです。
私は何が欠けていますか?
これは主に Dependency Injection に使用されます(ケーキパターンなど)。 すばらしい記事 が存在し、Cake Patternを含むScalaの多くの異なる形式の依存性注入をカバーしています。 「Cake Pattern and Scala」をGoogleで検索すると、プレゼンテーションやビデオなど、多くのリンクが表示されます。今のところ、 別の質問 へのリンクがあります。
さて、自己型と特性の拡張の違いは何ですか、それは簡単です。 B extends A
と言う場合、B
isはA
です。自己型を使用する場合、B
にはA
が必要です。セルフタイプで作成される2つの特定の要件があります。
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
のサブクラスである場合、エラーは発生しません。上記のコードでは、User
が使用されるたびにTweeter
をrequiredしましたが、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
を混在させる要件が満たされます。ただし、上記の2番目の要件は満たされていません。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 => ...
お役に立てれば。
もう1つの違いは、自己型が非クラス型を指定できることです。例えば
trait Foo{
this: { def close:Unit} =>
...
}
ここでの自己型は構造型です。その効果は、Fooで混合するものはすべて、ユニットを返す引数なしの「close」メソッドを実装する必要があるということです。これにより、カモタイピング用の安全なミックスインが可能になります。
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;
}
}
言及されていないもう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
TL; DRのその他の回答の要約:
拡張する型は継承型に公開されますが、自己型は公開されません
例:class Cow { this: FourStomachs }
は、反allows動物のみが利用できるdigestGrass
などのメソッドを使用できます。ただし、Cowを拡張する特性にはそのような特権はありません。一方、class Cow extends FourStomachs
はextends Cow
の誰にでもdigestGrass
を公開します。
自己型は循環依存を許可しますが、他の型を拡張することはできません
周期的な依存関係から始めましょう。
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]
ScnBase
は Swing Frameクラスを継承するため、自己型として使用し、最後に(インスタンス化時に)混合することができます。ただし、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
}
自己タイプを使用すると、特性をミックスインできるタイプを指定できます。たとえば、自己タイプCloseable
を持つ特性がある場合、その特性は、それを混ぜることが許可されているものだけがCloseable
インターフェースを実装する必要があることを知っています。
更新:主な違いは、セルフタイプは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
ミックスインを追加できます。もちろん、これはExpense
がPerson
を拡張する場合、またはその逆の場合にのみ意味があります。ポイントは、自己型Employee
を使用することは、依存するクラスの階層から独立している可能性があるということです。何が何を拡張するかは気にしません-Expense
とPerson
の階層を切り替えれば、Employee
を変更する必要はありません。
前者の場合、Bのサブ特性またはサブクラスは、Aを使用するものに混ぜることができます。したがって、Bは抽象特性になります。