抽象クラスのメソッドを強制して、呼び出されるオブジェクトのconcreteクラスの戻り値の型を持たせる方法が必要です。最も一般的な例は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)
}
この素晴らしい答え のアプローチを含め、私はすでに多くのアプローチを見てきました。ただし、実際にはどれもforces独自の型を返す実装ではありません。たとえば、次のクラスが有効です。
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
の与えられたサブクラスのものであるというオブジェクトのコピーをしている場合、それを引き起こします:
// type error: Seq[A] is not a Seq[CA]!
def createCopies[CA <: A](seq: Seq[CA]): Seq[CA] = seq.map(_.copy(genNewId()))
私がそれを行うことができるより良い、タイプセーフな方法はありますか?
編集:可能であれば、抽象クラスの任意の深い階層を作成する機能を維持したいと思います。つまり、前の例では、A
を拡張するabstractクラスA2
を作成してから、A2
の具象サブクラスの作成に進むことができると期待しています。ただし、それによって問題が単純化される場合(抽象型の場合のように)、すでに具体的なクラスをさらに拡張する必要はありません。
私が考えることができた唯一の解決策はこれでした:
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
}
宣言側で型を強制的にバインドしないでくださいA
itelfの定義内でそのバインドが必要な場合を除く。以下で十分です。
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()))
scalaではあなたがやりたいことをすることは不可能だと思います。
私がした場合:
_class Base { type A }
class Other extends Base
class Sub extends Other
_
ここで...タイプAが「サブクラスのタイプ」を参照するようにします。
Base
のコンテキストから、特定の「サブクラスのタイプ」が何を意味するかは(コンパイラの観点から)特に明確ではなく、親でそれを参照する構文が何であるかを気にしないでください。 Other
ではOther
のインスタンスを意味しますが、SubではSub
のインスタンスを意味する場合がありますか? Other
ではなくOther
でSub
を返すメソッドの実装を定義しても問題ありませんか? A
を返す2つのメソッドがあり、1つがOther
に実装され、もう1つがSub
に実装されている場合、Baseで定義された型には、同時に2つの異なる意味/境界/制限があることを意味しますか?では、A
がこれらのクラスの外部で参照されている場合はどうなりますか?
私たちが持っている最も近いものは_this.type
_です。 _this.type
_の意味を緩和する(またはより緩和されたバージョンを提供する)ことが理論的に可能かどうかはわかりませんが、実装すると、非常に特殊な型を意味するため、_def foo:this.type
_はthis
自体です。
私はあなたが提案したことをしたいのですが、それがどのように機能するかわかりません。 _this.type
_が...もっと一般的なことを意味していると想像してみましょう。それはどうなりますか? _class Subclass with MyTrait{type A=MyTrait}
_を有効にしたくないので、「this
の定義されたタイプのいずれか」とだけ言うことはできません。 「this
のすべてのタイプを満たすタイプ」と言うこともできますが、誰かが_val a = new Foo with SomeOtherMixin
_...と書くと混乱します。それでも、次の実装を可能にする方法で定義できるかどうかはわかりません。上で定義したOther
とSub
の両方。
私たちは、静的な型と動的に定義された型を混ぜ合わせようとしています。
Scalaでは、_class B { type T <: B }
_と言うと、T
はインスタンスに固有であり、B
は静的です(Javaの静的メソッドの意味でそのWordを使用しています)。 class Foo(o:Object){type T = o.type}
と言うことができ、T
はインスタンスごとに異なります....しかし、_type T=Foo
_と書くと、Foo
は静的に指定されたクラスの型です。 _object Bar
_を持っていて、いくつかの_Bar.AnotherType
_を参照していた可能性もあります。 AnotherType
は本質的に「静的」であるため(Scalaでは実際には「静的」とは呼ばれていませんが)、Foo
の継承には参加しません。
ただし、実装が独自の型を返すように強制するものはありません。たとえば、次のクラスが有効です。
しかし、それは正常ではありませんか?そうでなければ、作成しようとしているコントラクト(つまり、新しいクラスのA
)が自動的に破られるため、例としてcopy
を拡張して新しいメソッドを追加することはできません。このクラスのインスタンスではなく、A
)のインスタンスを返します。クラスA
を拡張するとすぐに壊れてしまう、完全に細かいクラスB
を持つことができるという事実は、私には間違っていると感じます。しかし、正直なところ、私はそれが引き起こす問題について言葉を出すのに苦労しています。
[〜#〜] update [〜#〜]:これについてもう少し考えた後、型チェック( "return type == most-派生クラス")があれば、これは正しいと思います。具体的なクラスでのみ作成され、抽象クラスや特性では作成されません。 scala型システムでそれをエンコードする方法はわかりませんが、.
私がそれを行うことができるという事実は、私が持っている唯一の情報がAの特定のサブクラスのものであるというオブジェクトのコピーを行っている場合にそれを引き起こします
Seq[Ca#Self]
を返すことができないのはなぜですか?たとえば、この変更により、B
のリストをcreateCopies
に渡すと、期待どおりに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)