408

trait の自己型A:

trait B
trait A { this: B => }

は、A拡張もしない具象クラスに混在させることはできませんBと述べています。

一方で、次のようになります。

trait B
trait A extends B

は、「(具体的または抽象的な)クラスが混在するAと、Bにも混在する」と述べています。

この 2 つのステートメントは同じ意味ではないでしょうか? 自己型は、単純なコンパイル時エラーの可能性を生み出すためだけに役立つようです。

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

4

11 に答える 11

290

主に Cake パターンなどのDependency Injectionに使用されます。Cake パターンを含む、Scala でのさまざまな形式の依存性注入をカバーする素晴らしい記事が存在します。"Cake Pattern and 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、エラーは発生しません。上記のコードでは、whenを使用する必要がありましたが、 に が指定されていなかったため、エラーが発生しました。さて、上記のコードがまだ範囲内にあるので、次のことを検討してください。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両方の要件が満たされています。とのUser実装Userが提供されます。

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

于 2010-01-02T14:42:59.957 に答える
161

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

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

お役に立てれば。

于 2010-01-02T11:43:43.577 に答える
60

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

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

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

于 2010-06-21T15:04:44.780 に答える
13

Martin Odersky のオリジナルの Scala 論文Scalable Component Abstractionsのセクション 2.3「Selftype Annotations」では、mixin 合成を超えた selftype の目的を実際に非常によく説明しています。つまり、クラスを抽象型に関連付ける別の方法を提供します。

論文に示されている例は次のようなもので、エレガントなサブクラス対応者がないようです。

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;
  }
}
于 2014-09-14T01:09:14.193 に答える
10

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

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

ただし、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

Cake パターンの微妙な点をすべて理解しているとは言えませんが、モジュール性を強制する主な方法は、継承や自己型ではなく合成によるものだと思います。

継承バージョンの方が短いですが、self 型よりも継承を好む主な理由は、self 型で初期化順序を正しくするのがはるかに難しいからです。ただし、継承ではできなくても、self 型でできることがいくつかあります。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は、それ自体が継承できる複数の直交特性によって継承できます。複数の型パラメーター (ジェネリック) を使用すると、代替形式のモジュール性が提供されます。

于 2012-10-06T17:24:44.613 に答える
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
}
于 2012-10-22T15:32:39.710 に答える
4

self 型を使用すると、trait に mixin できる型を指定できます。たとえば、自己型の特性がある場合、Closeableその特性は、それを混在させることが許可されているものだけがCloseableインターフェイスを実装する必要があることを知っています。

于 2010-01-02T11:20:47.413 に答える
2

更新:主な違いは、自己型が複数のクラスに依存できることです(これは少し特殊なケースであることは認めます)。たとえば、次のことができます。

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

これにより、 andEmployeeのサブクラスにのみ mixinを追加できます。もちろん、これは拡張された場合、またはその逆の場合にのみ意味があります。要点は、自己型の使用は、それが依存するクラスの階層に依存しないということです。何が何を拡張するかは気にしません - vsの階層を切り替えれば、変更する必要はありません。PersonExpenseExpensePersonEmployeeExpensePersonEmployee

于 2012-10-07T07:42:40.617 に答える
0

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

于 2010-01-02T10:03:39.607 に答える