Dhruv Kapur
Dhruv Kapur

Reputation: 736

Scala - Passing a Seq/Map of derived class's class in place of base class's class

This is a follow up question on my previous question: Scala - Passing Class of derived type in place of Class of supertype

Following is my use case: I have a class called MultiplePredictionTester with the following implementation...

class MultiplePredictionTester[T <: SurvivalPredictor](predictorClasses: Seq[Class[T]],
                                                       restOfConstructorParams: Map[Class[T], Seq[Object]],
                                                       dataSplitter: DataSplitter,
                                                       titanic: DataFrame) extends PredictionTester {

  import titanic.sqlContext.implicits._

  override def test: Map[SurvivalPredictor, Double] = ???
}

The idea is to be able to take a sequence of classes derived from SurvivalPredictor and instantiate them inside, using a constructor where first argument is fetched from DataSplitter while the rest of the arguments come from the Map[Class[T], Seq[Object]]

I plan to invoke this class as:

object MultiQuickRunner extends App with ResourceGetter {
  val conf: SparkConf = new SparkConf().setAppName("TitanicSurvivalPredictor").setMaster("local[4]")
  val sc: SparkContext = new SparkContext(conf)
  val sqlContext: SQLContext = new SQLContext(sc)
  val test: Map[SurvivalPredictor, Double] =
    new MultiplePredictionTester(Seq(classOf[SexBasedPredictor], classOf[TotallyRandomPredictor]),
      Map[Class[SurvivalPredictor],Seq[Object]](),
      new DataSplitter {},
      new DataFrameLoader {}.load(getPathForResource("train.csv"),
        sqlContext)).test
  test.foreach { case (survivalPredictorInstance, accuracy) =>
    println("Accuracy for " + survivalPredictorInstance.getClass.getName + " is " + accuracy)
  }
}

However trying to compile something like that throws me the exception:

Error:(27, 5) no type parameters for constructor MultiplePredictionTester: (predictorClasses: Seq[Class[T]], restOfConstructorParams: Map[Class[T],Seq[Object]], dataSplitter: com.dhruvk.kaggle.DataSplitter, titanic: org.apache.spark.sql.DataFrame)com.dhruvk.kaggle.predictiontesters.implementations.MultiplePredictionTester[T] exist so that it can be applied to arguments (Seq[Class[_ >: com.dhruvk.kaggle.predictors.implementations.TotallyRandomPredictor with com.dhruvk.kaggle.predictors.implementations.SexBasedPredictor <: com.dhruvk.kaggle.predictors.SurvivalPredictor]], scala.collection.immutable.Map[Class[com.dhruvk.kaggle.predictors.SurvivalPredictor],Seq[Object]], com.dhruvk.kaggle.DataSplitter, org.apache.spark.sql.DataFrame)
 --- because ---
argument expression's type is not compatible with formal parameter type;
 found   : Seq[Class[_ >: com.dhruvk.kaggle.predictors.implementations.TotallyRandomPredictor with com.dhruvk.kaggle.predictors.implementations.SexBasedPredictor <: com.dhruvk.kaggle.predictors.SurvivalPredictor]]
 required: Seq[Class[?T]]
    new MultiplePredictionTester(Seq(classOf[SexBasedPredictor], classOf[TotallyRandomPredictor]),
    ^ 

Any help is appreciated.

Upvotes: 0

Views: 511

Answers (2)

Alexey Romanov
Alexey Romanov

Reputation: 170735

As my comment to Till Rohrman's answer to the linked question mentions, there is just one Class[T] for any specific class or trait T: classOf[T]. So your predictorClasses can have length at most 1, and restOfConstructorParams can have size of most one. This is not what you want. The correct signature would use existential types:

class MultiplePredictionTester(predictorClasses: Seq[Class[_ <: SurvivalPredictor]],
                               ...

Class[_ <: SurvivalPredictor] is short for Class[T <: SurvivalPredictor] forSome { type T } and is the common type for all Class objects of subtypes of SurvivalPredictor.

It isn't clear what you want to use restOfConstructorParams for, but the type you gave is also wrong. It might be Seq[Any], for example.

Upvotes: 1

Johny T Koshy
Johny T Koshy

Reputation: 3887

java.lang.Class is invariant. During class declaration you are telling compiler that it is some T which has a upper bound such that T <: SurvivalPredictor. But when initializing you are saying T could SexBasedPredictor, TotallyRandomPredictor or SurvivalPredictor. This is not possible.

Consider

class A
class B extends A

class Test[T <: A](x: Seq[Class[T]], m: Map[Class[T], Seq[T]])

Here I have a class Test with type parameter T with upper bound A. Now I can initialize Test with

new Test(Seq(classOf[A], classOf[A]), Map(classOf[A] -> Seq(new B)))

As java.lang.Class is invariant, once I state T as A. I cant change it. If I change to B, compiler will yell at me. On the other hand as Seq is covariant I can say new B there.

Upvotes: 1

Related Questions