List of Runners vs Implicits


#1

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?