schmmd
schmmd

Reputation: 19478

Scala copy method in supertype

I have an abstract class (this is an analogous example) Printer which prints the supplied argument unless it fails to pass a filter. Printer is contravariant w.r.t. its generic type T.

class Printer[-T] {
  val filters: Seq[Function2[T => Boolean]]

  def print(t: T): Unit {
    if (filters.forall(_(t))) doPrint(t)
  }
  def doPrint(t: T): Unit
}

I now have 20 subclasses of Printer -- one for Strings, Ints, etc. Since Printer is contravariant, filters must be a val. However if I want a method of Printer to add a filter it needs to be immutable.

def addFilter[TT <: T](t: TT): Printer[TT]

Unfortunately I now need to implement this method in each of my 20 subclasses. Is there any way around this?

Update: Also, in addFilter, I don't know how I can return the subclass instead of the superclass Printer. For example, if I called addFilter on a StringPrinter I would ideally get the type StringPrinter back.

Upvotes: 1

Views: 200

Answers (1)

Shadowlands
Shadowlands

Reputation: 15074

THe following is a bit of a departure from the way you have coded your Printer, but potentially could fulfil your intentions. Note that this way of coding your class enables a greater separation of concerns: you can define filters and print implementations (the doPrint argument) independently of how they are being used:

case class Printer[T](val filters: List[Function1[T, Boolean]], val doPrint: T => Unit) {
  def print(t: T): Unit = {
    if (filters.forall(_(t))) doPrint(t)
  }

  def addFilter[TT <: T](f: TT => Boolean): Printer[TT] = copy(f :: filters)
}

Note that I haven't needed to specify contravariancy here, not sure if that will be an issue for you.

To use the class, you don't need to subclass, just pass suitable arguments into the constructor (actually, the companion apply factory method provided for free for case classes) - eg:

case class Foo(x: Int)
val fooChk1: Foo => Boolean = (f: Foo) => f.x != 1
val fooPrinter1 = Printer(fooChk1 :: Nil, (f: Foo) => println(s"Foo: x = ${f.x}"))

val fooChk3: Foo => Boolean = (f: Foo) => f.x != 3
val fooPrinter2 = fooPrinter1.addFilter(fooChk3)

val foo3 = Foo(3)
fooPrinter1.print(foo3) // Prints 'Foo: x = 3'
fooPrinter3.print(foo3) // Prints nothing

There is also a fair bit of scope for using implicits here, too - both for parameters to Print (eg. change the constructor to (val filters: List[Function1[T, Boolean]])(implicit val doPrint: T => Unit)), and for specific Print[X] variants.

Upvotes: 1

Related Questions