web-dev-qa-db-ja.com

Scala:具体的なインスタンスの戻り値の型でメソッドを実装する

抽象クラスのメソッドを強制して、呼び出されるオブジェクトの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の具象サブクラスの作成に進むことができると期待しています。ただし、それによって問題が単純化される場合(抽象型の場合のように)、すでに具体的なクラスをさらに拡張する必要はありません。

36
Rui Gonçalves

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

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
}
26
EECOLOR

宣言側で型を強制的にバインドしないでください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()))
4
0__

scalaではあなたがやりたいことをすることは不可能だと思います。

私がした場合:

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

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

Baseのコンテキストから、特定の「サブクラスのタイプ」が何を意味するかは(コンパイラの観点から)特に明確ではなく、親でそれを参照する構文が何であるかを気にしないでください。 OtherではOtherのインスタンスを意味しますが、SubではSubのインスタンスを意味する場合がありますか? OtherではなくOtherSubを返すメソッドの実装を定義しても問題ありませんか? 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_...と書くと混乱します。それでも、次の実装を可能にする方法で定義できるかどうかはわかりません。上で定義したOtherSubの両方。

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

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の継承には参加しません。

1
nairbv

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

しかし、それは正常ではありませんか?そうでなければ、作成しようとしているコントラクト(つまり、新しいクラスの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)
0