36

抽象クラスのメソッドを強制して、呼び出されるオブジェクトの具象クラスの戻り型を持たせる方法が必要です。最も一般的な例はcopy()メソッドであり、現在、抽象型に基づくアプローチを使用しています。

abstract class A(id: Int) {
  type Self <: A
  def copy(newId: Int): Self
}

class B(id: Int, x: String) extends A(id) {
  type Self = B
  def copy(newId: Int) = new B(newId, x)
}

class C(id: Int, y: String, z: String) extends A(id) {
  type Self = C
  def copy(newId: Int) = new C(newId, y, z)
}

私はすでにこの素晴らしい答えのアプローチを含む多くのアプローチを見ました。ただし、実際には、実装に独自の型を返すように強制するものはありません。たとえば、次のクラスが有効になります。

class D(id: Int, w: String) extends A(id) {
  type Self = A
  def copy(newId: Int) = new D(newId, w) // returns an A
}

class E(id: Int, v: String) extends A(id) {
  type Self = B
  def copy(newId: Int) = new B(newId, "")
}

私がそれを行うことができるという事実は、私が持っている唯一の情報がA'sの与えられたサブクラスのものであるというオブジェクトのコピーをしている場合、それを引き起こします:

// type error: Seq[A] is not a Seq[CA]!
def createCopies[CA <: A](seq: Seq[CA]): Seq[CA] = seq.map(_.copy(genNewId()))

私がそれを行うことができるより良い、タイプセーフな方法はありますか?

編集:可能であれば、抽象クラスの任意の深い階層を作成する機能を維持したいと思います。つまり、前の例では、を拡張する抽象クラスを作成してから、の具象サブクラスの作成に進むことができると期待しています。ただし、それによって問題が単純化される場合(抽象型の場合のように)、すでに具体的なクラスをさらに拡張する必要はありません。A2AA2

4

4 に答える 4

26

私が考えることができる唯一の解決策はこれでした:

trait CanCopy[T <: CanCopy[T]] { self: T =>
  type Self >: self.type <: T
  def copy(newId: Int): Self
}

abstract class A(id: Int) { self:CanCopy[_] =>
  def copy(newId: Int): Self
}

以下はコンパイルされます:

class B(id: Int, x: String) extends A(id) with CanCopy[B] {
  type Self = B
  def copy(newId: Int) = new B(newId, x)
}

class C(id: Int, y: String, z: String) extends A(id) with CanCopy[C] {
  type Self = C
  def copy(newId: Int) = new C(newId, y, z)
}

以下はコンパイルされません。

class D(id: Int, w: String) extends A(id) with CanCopy[D] {
  type Self = A
  def copy(newId: Int) = new D(newId, w) // returns an A
}

class E(id: Int, v: String) extends A(id) with CanCopy[E] {
  type Self = B
  def copy(newId: Int) = new B(newId, "")
}

編集

実はcopyメソッドを削除するのを忘れていました。これはもう少し一般的かもしれません:

trait StrictSelf[T <: StrictSelf[T]] { self: T =>
  type Self >: self.type <: T
}

abstract class A(id: Int) { self:StrictSelf[_] =>
  def copy(newId:Int):Self
}
于 2013-02-16T12:30:29.773 に答える
4

itelfの定義内でバインドする必要がない限りA、宣言側で型バインドを強制しないでください。以下で十分です。

abstract class A(id: Int) {
  type Self
  def copy(newId: Int): Self
}

Self次に、使用サイトでタイプを強制します。

def genNewId(): Int = ???
def createCopies[A1 <: A { type Self = A1 }](seq: Seq[A1]): Seq[A1] = 
  seq.map(_.copy(genNewId()))
于 2013-02-16T00:44:44.547 に答える
1

あなたが望むことをscalaで行うことは不可能だと思います。

私だったら:

class Base { type A }
class Other extends Base
class Sub extends Other

ここで... タイプ A が「サブクラスのタイプ」を参照するようにします。

のコンテキストからBase、特定の「サブクラスの型」が何を意味するのか (コンパイラの観点から) 特に明確ではないことがわかります。親でそれを参照する構文がどうなるかは気にしないでください。Otherのインスタンスを意味しますOtherが、Sub では ? のインスタンスを意味する場合がありますSubOtherinOtherではなく inを返すメソッドの実装を定義してもよろしいでしょうSubか? を返す 2 つAのメソッドがあり、1 つが で実装され、もう 1 つが で実装されOtherている場合、それSubは Base で定義された型が同時に 2 つの異なる意味/境界/制限を持っていることを意味しますか? Aでは、これらのクラスの外で が参照されるとどうなるでしょうか?

私たちが持っている最も近いものはですthis.type。の意味を緩和する (またはより緩和されたバージョンを提供する) ことが理論的に可能かどうかはわかりませんthis.typeが、実装されると非常に具体的な型を意味するため、条件を満たす唯一の戻り値def foo:this.typethisそれ自体です。

教えていただいたことを実行したいのですが、どうすればよいかわかりません。それが意味することを想像してみましょうthis.type...もっと一般的なものです。それはどうなりますか?有効にしthisたくないので、「の定義された型のいずれか」とだけ言うことはできません。class Subclass with MyTrait{type A=MyTrait}「 のすべての型を満たす型」と言うことができますがthis、誰かが書くと混乱します...そして、上記で定義されたとval a = new Foo with SomeOtherMixinの両方の実装を可能にする方法で定義できるかどうかはまだわかりません。OtherSub

私たちは、静的な型と動的に定義された型を混在させようとしています。

Scala では、class B { type T <: B }Tインスタンスに固有であり、B静的です (Java の静的メソッドの意味でこの言葉を使用しています)。と言うことができclass Foo(o:Object){type T = o.type}Tインスタンスごとに異なります....しかし、書くとtype T=FooFooクラスの静的に指定された型です。object Barを持っていて、いくつかの を参照していた可能性もありBar.AnotherTypeます。はAnotherType本質的に「静的」であるため (Scala では実際には「静的」とは呼ばれませんが)、 では継承に参加しませんFoo

于 2013-02-16T16:00:10.097 に答える
0

ただし、実装が独自の型を返すことを実際に強制するものはありません。たとえば、次のクラスが有効です。

でも普通じゃない?それ以外の場合は、作成しようとしているコントラクトが自動的に破棄されるため、単純に拡張して新しいメソッドを例に追加することはできませんA(つまり、新しいクラスcopyはこのクラスのインスタンスを返すのではなく、のインスタンスを返しますA) 。 . Aクラスとしてクラスを拡張するとすぐに壊れる、完全に優れたクラスを持つことができるというまさにその事実は、B私には間違っていると感じています。しかし、正直なところ、それが引き起こす問題について言葉にするのは難しいです。

更新:これについてもう少し考えた後、型チェック(「型を返す==最も派生したクラス」)が具体的なクラスでのみ行われ、抽象クラスや特性では行われなかった場合、これは適切であると思います。ただし、scala型システムでそれをエンコードする方法は知りません。

私がそれを行うことができるという事実は、私が持っている唯一の情報がAの特定のサブクラスであるというオブジェクトのコピーを行っている場合、それを引き起こします

を返すことができないのはなぜSeq[Ca#Self]ですか?例として、この変更によりBtoのリストを渡すcreateCopiesと、期待どおりに a (および:Seq[B]だけでなく) が返されます。Seq[A]

scala> def createCopies[CA <: A](seq: Seq[CA]): Seq[CA#Self] = seq.map(_.copy(123))
createCopies: [CA <: A](seq: Seq[CA])Seq[CA#Self]

scala> val bs = List[B]( new B(1, "one"), new B(2, "two"))
bs: List[B] = List(B@29b9ab6c, B@5ca554da)

scala> val bs2: Seq[B] = createCopies(bs)
bs2: Seq[B] = List(B@92334e4, B@6665696b)
于 2013-02-06T14:47:43.537 に答える