Subclasses and return types

2019-08-08 02:33发布

问题:

let's say I wanted to have something like the following:

abstract class PDF[T, S <: PDF[T, _]] {
  def fit(obs: Seq[T], weights: Seq[Double]): S
}

class PDFGaussian(val mu: Double, val Sigma: Double) extends PDF[Double, PDFGaussian] {
  def fit(obs: Seq[Double], weights: Seq[Double]): PDFGaussian =
    new PDFGaussian(...) // bla bla bla
}

So, basically, what I want is to have the fit function to return an instance of the type of its enclosing class which obviously must be a subclass of PDF[T]. However, instead of having to use the double parameterization PDF[T, S <: PDF[T, _]] I'd rather go with only one type parameter like so:

abstract class PDF[T] {
  def fit[S <: PDF[T]](obs: Seq[T], weights: Seq[Double]): S
}

class PDFGaussian(val mu: Double, val Sigma: Double) extends PDF[Double] {
  def fit[S <: PDF[_]](obs: Seq[Double], weights: Seq[Double]): S =
    new PDFGaussian(...) // bla bla bla
}

If I do this, however, the compiler yells at me for returning PDFGaussian instead of S. Since I'm obviously missing some important fact about scala's type system here could you please clarify what I'm doing wrong and show me how to do it with only one type parameter?

回答1:

Your first solution is pretty good, IMHO. But let's talk about the questions. First, about what is wrong here:

abstract class PDF[T] {
  def fit[S <: PDF[T]](obs: Seq[T], weights: Seq[Double]): S
}

class PDFGaussian(val mu: Double, val Sigma: Double) extends PDF[Double] {
  def fit[S <: PDF[_]](obs: Seq[Double], weights: Seq[Double]): S =
    new PDFGaussian(...) // bla bla bla
}

Let's say I have

class FooBar extends PDF[Double] { ... }

And I do:

val pdfg = new PDFGaussian(1.0, -1.0)
val foobar = pdfg.fit[FooBar](List(0.5, 0.75), List(4, 2))

So, I'm telling the compiler that I want S to be FooBar, but you are returning PDFGaussian! That's what the compiler is complaining about.

So, how to solve it? Well... tough. :-) How about this:

abstract class PDF[T] {
  type S <: PDF[T]
  def fit(obs: Seq[T], weights: Seq[Double]): S
}

class PDFGaussian(val mu: Double, val Sigma: Double) extends PDF[Double] {
  type S = PDFGaussian
  def fit(obs: Seq[Double], weights: Seq[Double]): S =
    new PDFGaussian(...) // bla bla bla
}

It's a bit more verbose, but it keeps PDF type signature cleaner.