Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Derived classes as class constructor parameters

[EDITED BELOW]

I have a class hierarchy that is, right now, roughly as follows:

sealed trait Foo {
  def method: Any
}

case class Bar extends Foo {
  def method: Array[String] = // implementation
}

case class Baz extends Foo {
  def method: Map[String, Array[String]] = // implementation
}

I am giving the abstract method a return type of Any because the return types of the case classes are necessarily different, but they share a similar purpose. For this reason, I'd like to keep it in the trait to model that common behavior, and this is the only way I've found to make it compile. I realize this is against the spirit of Scala's type system, so I ask the first question below.

Then, another class expects a subclass of Foo as a constructor parameter, and I do not know how to denote this, other than the following:

class Qux(foo: Foo) {
  val m = foo.method
  ...
  ...
}

Later in the class Qux there are methods that expect the val m to be of the type corresponding to the particular subclass (Bar or Baz) of Foo, but I am getting compilation errors like

... type mismatch;
[error]  found   : Any
[error]  required: Array[String]

So I have a couple questions:

  • I am familiar enough with Scala to believe that this is the right way to represent my particular problem, but not familiar enough with it to know how to go about it. What is the proper way to do what I'm trying to do?
  • Also, is there a way to tell class Qux that m should be treated as the value returned by the specific method from Bar or Baz, and not the abstract method from Foo?

Edit: Taking the approach suggested by @marios (using abstract type members) seems to be a step in the right direction, but a type mismatch pops up now. Within the class Qux, I now have

class Qux[X <: Foo](sc: SparkContext, val foo: X) {
  val m: foo.A = foo.method

  def process(rows: DataFrame) = foo match {
    case Bar(sc, _) => BarProcessor(sc, m).doStuff(rows)
    case Baz(sc, _) => BazProcessor(sc, m.keys).doStuff(rows, m.values)
  }
}

Where BarProcessor is instantiated with, for instance, an Array[String], and BazProcessor needs the key-value pairs from the value returned by Baz's method to do stuff. However, I am now getting errors like

[error] Qux.scala:4: type mismatch;
[error]  found   : Qux.this.foo.A
[error]  required: Array[String]
[error]     case Bar(sc, _) => BarProcessor(sc, m).doStuff(rows)
[error]                                         ^

Similar errors show up when I try to call Map-specific methods on m when foo is a Baz (along the lines of value keys is not a member of Qux.this.foo.A, etc.). I understand that m isn't really an Array[String] -- it's of type A. But is there a way to tell Scala to "translate" this into its desired type?

like image 250
user4601931 Avatar asked Feb 06 '23 06:02

user4601931


2 Answers

An easier way to access the individual types in your ADT is to use an abstract type member instead of a generic type parameter.

sealed trait Foo {
   type A
   def method: A
}

case object Bar extends Foo {
   type A = Array[String]
   def method: A = Array.empty[String]
}

case object Baz extends Foo {
   type A = Map[String, Array[String]]
   def method: A = Map.empty[String, Array[String]]
}

case class Qux[X <: Foo](foo: X) {
  def m: X#A = foo.method

  // You can then pattern match on m
  def f = m match {
    case a: Baz.A => a.size // Use Baz#A if Baz is a class and not an object
    case b: Bar.A => b.size // Use Bar#A if Bar is a class and not an object
  }
}

Using it (look at the return types)

@ Qux(Baz).m
res6: Map[String, Array[String]] = Map()

@ Qux(Bar).m
res7: Array[String] = Array()
like image 119
marios Avatar answered Feb 12 '23 18:02

marios


You could add a type parameter to your trait like this:

sealed trait Foo[A] {
  def method: A
}

case class Bar extends Foo[Array[String]] {
  def method: Array[String]
}

case class Baz extends Foo[Map[String, Array[String]]] {
  def method: Map[String, Array[String]]
}
like image 39
Tyler Avatar answered Feb 12 '23 18:02

Tyler