0

I have a base class named named like the following. Its primary constructor takes 4 parameters, flow1 and flow2 are of type T0 and T1, which are derived from Flow, an abstract class. The predicate parameter is a object of FlowParser, for different EitherFlow, there can be different FlowParser.

Now I want to remove in subclasses.

import scala.reflect.ClassTag
import scala.reflect._


abstract class Flow() extends Serializable {

}

trait FlowParser extends Serializable {
  def judgeIndex(path: String): Int
  def createFlow(path: String): Flow
}

class EitherFlow[T0 >: Null <: Flow: ClassTag, T1 >: Null <: Flow: ClassTag](
    predicate: FlowParser,
    var flow1: T0,
    var flow2: T1,
    var path: String) extends Flow{

  def this(predi: FlowParser, path: String) = this(predi, null, null, path)

  def this(predi: FlowParser) = this(predi, "")

  protected def this(predi: FlowParser, index: Int, d: Flow) {
    this(predi, null, null, "")
    if (index == 0) {
      this.flow1 = d.asInstanceOf[T0]
    } else {
      this.flow2 = d.asInstanceOf[T1]
    }
  }

  def this(predi: FlowParser, d: Flow) {
    this(predi, null, null, "")
    d match {
      case _: T0 => this.flow1 = d.asInstanceOf[T0]
      case _: T1 => this.flow2 = d.asInstanceOf[T1]
    }
  }
}

Now I have many subclasses from EitherFlow, such as EitherFlow[DataFrameFlow, RawFlow]. So I may write code like this

new EitherFlow[DataFrameFlow, RawFlow](new MyAnnoyingFlowParser(), "")

However, new MyAnnoyingFlowParser() is not necessary, because given T0 and T1 are certain, the type of MyAnnoyingFlowParser is certain, so actually I want this:

new EitherDataFrameOrRawFlow("")

UPDATE:

There are many constructors in EitherFlow, I want to do the following

// original
new EitherFlow[DataFrameFlow, RawFlow](new MyAnnoyingFlowParser(), a, b, c, ...)
// what I want
new EitherDataFrameOrRawFlow(a, b, c, ...)

I have tried the following strategies, and I meet some problems in both of them.

  1. Try "currying" the apply method With this strategy, I want I can write this

    val f1 = EitherDataFrameOrRawFlow("")
    val f2 = EitherDataFrameOrRawFlow(new RawFlow(), "")
    

    So I decide to write the following code, it actually returns a anonymous Object, which has overloaded apply method. I adopt this strategy from this post(Scala, currying and overloading)

    object EitherFlow {
      def apply[T0 >: Null <: Flow: ClassTag, T1 >: Null <: Flow: ClassTag](predicate: FlowParser) =
        new {
          def apply[R >: Null <: Flow: ClassTag](flow: R, path: String): EitherFlow[T0, T1] = {
            val ret = new EitherFlow[T0, T1](predicate, flow)
            ret.path = path
            ret
          }
          def apply(path: String): EitherFlow[T0, T1] = {
            new EitherFlow[T0, T1](predicate, null.asInstanceOf[T0], null.asInstanceOf[T1], path)
          }
        }
    }
    

    However, with this strategy, I have to use .apply explicitly. Or, it will cause an error

    com.xxx.flow.EitherDataFrameOrRawFlow.type does not take parameters
    
  2. Try create a new class and implement its own aux constructors

    However, I am confused when I try to implememt aux constructor def this(d: Flow). According to alexander, I can't directly call super's aux constructor. So I have to write like the following

    class EitherDataFrameOrRawFlow(var f1: DataFrameFlow, var f2: RawFlow, path: String) extends EitherFlow[DataFrameFlow, RawFlow](new DataFrameRawFlowParser(), f1, f2, path) {
      def this(d: Flow) {
        this(null, null, "")
        d match {
          case _: DataFrameFlow => this.flow1 = d.asInstanceOf[DataFrameFlow]
          case _: RawFlow => this.flow2 = d.asInstanceOf[RawFlow]
        }
      }
    }
    

    However, I think the code is not neat. Firstly, I have to write duplicate code such as d match {...}. Second, I have to define duplicate parameters suchas f1/f2.

calvin
  • 2,125
  • 2
  • 21
  • 38
  • Your code you've started with doesn't compile with `not enough arguments for constructor Flow`: https://scastie.scala-lang.org/MzEiQW61Ta6ui56LzSSpyA If I comment out `(var outputName: String)` it doesn't compile with `ambiguous implicit values`: https://scastie.scala-lang.org/qeA70jcER4qj43CUYCe30A . Please provide [MCVE](https://stackoverflow.com/help/minimal-reproducible-example). – Dmytro Mitin Sep 24 '20 at 09:44
  • 2
    Hi @DmytroMitin , I have [my codes edited](https://scastie.scala-lang.org/pfkkMR3PTViqGwSTJYFglg), and I use Scala 2.11.8, which can compile – calvin Sep 24 '20 at 09:55

1 Answers1

2

Looking at your code, it seems there would be only one correct implementation for DataFlowParser. Making that implicit is then the logical route. But we can't see that far into your design and constraints.

Given the constraints

  • flow1 and flow2 are both var's, both or neither can be null, both have an available classtag that adequately identifies the values in the face of erasure (so they don't have a type parameter)
  • you want to be able to call EitherDataFrameOrRawFlow(fl: Flow) and return a value of type EitherFlow[DataFrameFlow, RawFlow]

I'd just make

object EitherDataFrameOrRawFlow {
  def apply(fl: Flow) = new EitherFlow[DataFrameFlow, RawFlow](new MyAnnoyingFlowParser(), fl)
}

and not make a subclass at all.

I'm not clear on whether you pass a new MyAnnoyingFlowParser or a new DataFrameRawFlowParser, but you can switch that out yourself.

Edit: you can make a base class for the objects too:

abstract class PartiallyAppliedEitherConstructor[Flow1, Flow2](parser: => FlowParser) {
  def apply(fl: Flow) = new EitherFlow[Flow1, Flow2](parser)
  def apply(index: Int, flow: Flow) = new EitherFlow[Flow1, Flow2)(parser, index, flow)
  //etc
}

object EitherDataFrameOrRawFlow extends PartiallyAppliedEitherConstructor[DataframeFlow, RawFlow](new MyAnnoyingFlowParser())

object EitherRddOrDataFrameFlow extends PartiallyAppliedEitherConstructor[RddFlow, DataFromeFlow](new SomeOtherFlowParser())

//etc
Martijn
  • 11,964
  • 12
  • 50
  • 96
  • Hi Martjin, it is not true that "it seems there would be only one correct implementation for DataFlowParser". For `EitherFlow[DataFrameFlow, RawFlow]` I can have `EitherDataFrameOrRawFlow`, However, there will be other `EitherFlow`s, such as `EitherFlow[DataFrameFlow, RDDFlow]`, so there will be multiple subclass which inherits `FlowParser` – calvin Sep 24 '20 at 11:22
  • And I have considered your solution, it is practical, however, for every subclass of `EitherFlow`, I have to implement multiple overloads of `apply`, which simply forwards some of its arguments(except the first `FlowParser` argument), I think it results in lots of redundant code. – calvin Sep 24 '20 at 11:26
  • @calvin multiple apply methods for the specialized type wasn't part of your original question. Can you concisely indicate what exactly your design constraints are and what must by implemented? – Martijn Sep 24 '20 at 11:31
  • You said "so they don't have a type parameter", however, I think if `T0` and `T1` are not visible in runtime, should the compiler warns "is unchecked since it is eliminated by erasure" or something? However, I have seen no such warnings. And I don't know if there is any side effect if "don't have a type parameter", because pattern matching still works here? – calvin Sep 24 '20 at 11:35
  • Multiple apply methods for the specialized type is not in my question, because I already find [a solution to that](https://stackoverflow.com/questions/7179229/scala-currying-and-overloading) when I try the first strategy 1. However, there are other problems about strategy 1 – calvin Sep 24 '20 at 11:41
  • @calvin you only have a classtag, so you can't match on parameterized types. e.g. you can't see the difference between `T0 = MyFlow[Int]` and `T0 = MyFlow[String]`. But leaving that aside, what are the apply methods/constructors you need exactly for what parameters and with what return types? – Martijn Sep 24 '20 at 13:17
  • So I need a TypeTag? – calvin Sep 25 '20 at 02:16
  • Hi Martijn, Could you answer my prev question? – calvin Oct 26 '20 at 08:33
  • 1
    @calvin if you need to check whether some value has some specific type, where the type, you need a TypeTag, yes – Martijn Oct 26 '20 at 10:47