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

编辑:我刚刚意识到雷克斯在他的例子中有一个具体的类小说,而不是我在下面使用的特征。因此,特征实现有点过于简单,无法解决 Rex 的问题。它也可以使用具体的类来完成(见下文),但我可以做到这一点的唯一方法是通过一些强制转换,这使得这不是真正的“编译时类型安全”。因此,这不能作为解决方案。

也许不是最漂亮的,但是一个使用抽象成员类型的简单示例可以实现如下:


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")
}

并且代码将像在 trait 示例中一样工作。但它也遇到了与 Rex 在他的编辑中提到的相同的问题。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 或工厂或其他)保持抽象(在这里,我将所有内容归结为一种复制构造函数的方法),并且您必须添加MyType类型注释,如图所示。然后,在最后一步,必须实例化这些新复制方法。

这个策略也适用于协方差A,除了这个特定的例子不起作用,因为fg不是协变的。

于 2010-06-16T16:31:59.060 回答