15

新しいジェネリッククラスを構築したいとしNovel[A]ます。このクラスには多くの便利なメソッドが含まれているため(おそらくコレクションの一種である可能性があります)、サブクラス化する必要があります。ただし、元の型ではなく、サブクラスの型をメソッドが返すようにする必要があります。Scala 2.8では、そのクラスのメソッドが元のサブクラスではなく、関連するサブクラスを返すために必要な最小限の作業はどれくらいですか?例えば、

class Novel[A] /* What goes here? */ {
  /* Must you have stuff here? */
  def reverse/* What goes here instead of :Novel[A]? */ = //...
  def revrev/*?*/ = reverse.reverse
}
class ShortStory[A] extends Novel[A] /* What goes here? */ {
  override def reverse: /*?*/ = //...
}
val ss = new ShortStory[String]
val ss2 = ss.revrev  // Type had better be ShortStory[String], not Novel[String]

共変したい場合、この最小量は変わりますNovelか?

(2.8コレクションは、とりわけこれを行いますが、より派手な(そして便利な)方法でリターンタイプを操作します-問題は、このサブタイプ(常にリターンサブタイプ)のみが必要な場合に、フレームワークをどのように回避できるかです。特徴。)

編集:上記のコードでコピーを作成すると仮定しreverseます。インプレース修正を行ってから自分自身を返す場合は、を使用できますthis.typeが、コピーがではないため、それは機能しませんthis

Arjanは、次の解決策を提案する別の質問にリンクしました。

def reverse: this.type = {
  /*creation of new object*/.asInstanceOf[this.type]
}

これは基本的に、必要なものを取得するための型システムにあります。しかし、これは実際には解決策ではありません。型システムに嘘をついたので、コンパイラーは、私たちが思ったときに本当に戻ってくることを確認するのに役立ちませShortStory。(たとえばreverse、コンパイラーを満足させるために上記の例でオーバーライドする必要はありませんが、型は私たちが望んでいたものではありません。)

4

3 に答える 3

5

編集:レックスが彼の例で具体的なクラスの小説を持っていることに気づきました。以下で使用したような特性ではありません。したがって、特性の実装は、レックスの質問に対する解決策には少し単純すぎます。具象クラス(以下を参照)を使用して行うこともできますが、その作業を行う唯一の方法はキャストによるものであり、これは実際には「コンパイル時のタイプセーフ」ではありません。したがって、これは解決策とは見なされません。

おそらく最も美しいわけではありませんが、抽象メンバー型を使用した簡単な例を次のように実装できます。


trait Novel[A] { 
   type T <: Novel[A] 
   def reverse : T 
   def revrev : T#T = reverse.reverse 
}

class ShortStory[A](var story: String) extends Novel[A] {
 type T = ShortStory[A]
 def reverse : T = new ShortStory[A](story reverse)
 def myMethod: Unit = println("a short story method")
}

scala> val ss1 = new ShortStory[String]("the story so far")
ss1: ShortStory[String] = ShortStory@5debf305

scala> val ssRev = ss1 reverse 
ssRev: ss1.T = ShortStory@5ae9581b

scala> ssRev story
res0: String = raf os yrots eht

scala> val ssRevRev = ss1 revrev
ssRevRev: ss1.T#T = ShortStory@2429de03

scala> ssRevRev story
res1: String = the story so far

scala> ssRevRev myMethod
a short story method

確かに最小限ですが、これが一種のフレームワークとして使用するのに十分かどうかは疑問です。そしてもちろん、型はScalaコレクションフレームワークほど明確ではないので、おそらくこれは少し単純すぎるかもしれません。与えられたケースでは、しかし、それは仕事をしているようです。上で述べたように、これは与えられたケースではうまくいかないので、ここでは他の解決策が必要です。

さらに別の編集:具体的なクラスを使用して同様のことを行うこともできますが、タイプセーフにするのに十分ではありません。


class Novel[A](var story: String) {
  type T <: Novel[A] 
  def reverse: T = new Novel[A](story reverse).asInstanceOf[T]  
  def revrev : T#T = reverse.reverse
}
class ShortStory[A](var s: String) extends Novel[A](s) {
 type T = ShortStory[A]
 override def reverse : T = new ShortStory(story reverse)
 def myMethod: Unit = println("a short story method")
}

そして、コードはトレイトの例のように機能します。しかし、それはレックスが彼の編集で述べたのと同じ問題に苦しんでいます。このコンパイルを行うためにShortStoryのオーバーライドは必要ありません。ただし、これを行わずにShortStoryインスタンスでreverseメソッドを呼び出すと、実行時に失敗します。

于 2010-06-09T17:29:30.247 に答える
3

私はこれを完全に考えていませんが、型チェックは次のとおりです。

object invariant {
  trait Novel[A] {
    type Repr[X] <: Novel[X]

    def reverse: Repr[A]

    def revrev: Repr[A]#Repr[A]
       = reverse.reverse
  }
  class ShortStory[A] extends Novel[A] {
    type Repr[X] = ShortStory[X]

    def reverse = this
  }

  val ss = new ShortStory[String]
  val ss2: ShortStory[String] = ss.revrev
}

object covariant {
  trait Novel[+A] {
    type Repr[X] <: Novel[_ <: X]

    def reverse: Repr[_ <: A]

    def revrev: Repr[_ <: A]#Repr[_ <: A] = reverse.reverse
  }

  class ShortStory[+A] extends Novel[A] {
    type Repr[X] = ShortStory[X]

    def reverse = this
  }

  val ss = new ShortStory[String]
  val ss2: ShortStory[String] = ss.revrev
}

編集

共変バージョンは、はるかに優れています。

object covariant2 {
  trait Novel[+A] {
    type Repr[+X] <: Novel[X]

    def reverse: Repr[A]

    def revrev: Repr[A]#Repr[A] = reverse.reverse
  }

  class ShortStory[+A] extends Novel[A] {
    type Repr[+X] = ShortStory[X]

    def reverse = this
  }

  val ss = new ShortStory[String]
  val ss2: ShortStory[String] = ss.revrev
}
于 2010-06-09T22:03:53.447 に答える
2

Scala メーリング リストでの議論の後 (私を正しい軌道に乗せてくれた人々に感謝します!)、これが最小限のフレームワークに最も近いものだと思います。参照用にここに残しておきます。別の例を使用しています。これは、何がうまくいっているかを強調するためです。

abstract class Peano[A,MyType <: Peano[A,MyType]](a: A, f: A=>A) {
  self: MyType =>
  def newPeano(a: A, f: A=>A): MyType
  def succ: MyType = newPeano(f(a),f)
  def count(n: Int): MyType = {
    if (n<1) this
    else if (n==1) succ
    else count(n-1).succ
  }
  def value = a
}

abstract class Peano2[A,MyType <: Peano2[A,MyType]](a: A, f: A=>A, g: A=>A) extends Peano[A,MyType](a,f) {
  self: MyType =>
  def newPeano2(a: A, f: A=>A, g: A=>A): MyType
  def newPeano(a: A, f: A=>A): MyType = newPeano2(a,f,g)
  def pred: MyType = newPeano2(g(a),f,g)
  def uncount(n: Int): MyType = {
    if (n < 1) this
    else if (n==1) pred
    else uncount(n-1).pred
  }
}

ここで重要なのは、MyType実際に最終的に作成されるクラスの型のプレースホルダーである型パラメーターの追加です。継承するたびに、それを型パラメーターとして再定義する必要があり、この型の新しいオブジェクトを作成するコンストラクター メソッドを追加しました。コンストラクターが変更された場合は、新しいコンストラクター メソッドを作成する必要があります。

実際に使用するクラスを作成したい場合は、コンストラクター メソッドに new の呼び出しを入力するだけです (そして、それが独自の型であることをクラスに伝えます)。

class Peano2Impl[A](a: A, f: A=>A, g: A=>A) extends Peano2[A,Peano2Impl[A]](a,f,g) {
  def newPeano2(a: A, f: A=>A, g: A=>A) = new Peano2Impl[A](a,f,g)
}

そして、あなたはオフで実行中です:

val p = new Peano2Impl(0L , (x:Long)=>x+1 , (y:Long)=>x-1)

scala> p.succ.value
res0: Long = 1

scala> p.pred.value
res1: Long = -1

scala> p.count(15).uncount(7).value
res2: Long = 8

したがって、復習すると、最小限のボイラープレート (他のスタイルの回答を壊す再帰メソッドを含めたい場合) は、クラスの外部から (new または factory などを使用して) 新しいコピーを返すすべてのメソッド用です。抽象化したままにし (ここでは、コンストラクターを複製する 1 つのメソッドにすべてを煮詰めました)、MyType示されているように型注釈を追加する必要があります。次に、最後のステップで、これらの new-copy メソッドをインスタンス化する必要があります。

この戦略は共分散に対してAもうまく機能しますが、この特定の例は共変fgはないため機能しません。

于 2010-06-16T16:31:59.060 に答える