Hy!
I have a list of Steps (Step is a sealed trait), coming from DB or REST Api, so I can’t construct them from the code.
I want to run a specific task for each type of steps. I came up with 2 concepts, but I’m not happy with them.
object t1 {
class AbstractStepRunner[F[_] : ConcreteRunner[?[_], DummyStep]] {
def run(s: Step): F[Unit] = {
s match {
case ds: DummyStep => implicitly[ConcreteRunner[F, DummyStep]].run(ds)
}
}
}
trait ConcreteRunner[F[_], S <: Step] {
def run(s: S): F[Unit]
}
class DummyRunner[F[_]] extends ConcreteRunner[F, DummyStep] {
def run(s: DummyStep): F[Unit] = ???
}
}
With this implementation I need to create a Master matcher block, and I need to announce every new StepRunner implementation. At least the compiler is telling me if I left out some, so I will get compiler warning/errors, but I need to write a “lot of” boilerplate code.
object t2 {
class AbstractStepRunner[F[_]: MonadError[?[_], Throwable]](srl: Seq[ConcreteRunner[F, _]]) {
def run(s: Step) {
val pf: PartialFunction[Step, F[Unit]] = srl.map(_.tryToRun).reduceLeft(_ orElse _)
.orElse { case _ => implicitly[MonadError[F, Throwable]].raiseError[Unit](new Exception()) }
pf(s)
}
}
trait ConcreteRunner[F[_], S <: Step] {
implicit val ct: ClassTag[S]
def tryToRun: PartialFunction[Step, F[Unit]] = {case step:S => run(step)}
def run(s: S): F[Unit]
}
class DummyRunner[F[_]](implicit val ct: ClassTag[DummyStep]) extends ConcreteRunner[F, DummyStep] {
def run(s: DummyStep): F[Unit] = ???
}
}
My second idea was to construct a new PartialFunction from other partial functions, so I can use a list of runners as an input. I lost the compile time check, but my code is a bit smaller (at the scale of 20 steps), and maybe more readable.
What do you think, which one is better? What would you use to a similar problem?