St.Antario
St.Antario

Reputation: 27435

Configuring implicits in Scala

I have typeclass:

trait ProcessorTo[T]{
    def process(s: String): T
}

and its implementation

class DefaultProcessor extends ProcessorTo[String]{
    def process(s: String): String = s
}
trait DefaultProcessorSupport{
    implicit val p: Processor[String] = new DefaultProcessor
}

To make it available for using I created

object ApplicationContext
    extends DefaultProcessorSupport
    with //Some other typeclasses

But now I have to add a processor which performs some DataBase - read. The DB URL etc are placed in condifguration file that is available only a runtime. For now I did the following.

class DbProcessor extends ProcessorTo[Int]{
   private var config: Config = _
   def start(config: Config) = //set the configuration, open connections etc
   //Other implementation
}

object ApplicationContext{
    implicit val p: ProcessorTo[Int] = new DbProcessor
    def configure(config: Config) = p.asInstanceOf[DbProcessor].start(config)
}

It works for me, but I'm not sure about this technique. Looks strange for me a little bit. Is it a bad practice? If so, what would be a good solution?

Upvotes: 1

Views: 247

Answers (2)

SergGr
SergGr

Reputation: 23788

I don't really see why you need start. If your implicit DbProcessor has a dependency, why not make it an explicit dependency via constructor? I mean something like this:

class DbConfig(val settings: Map[String, Object]) {}

class DbProcessor(config: DbConfig) extends ProcessorTo[Int] {

  // here goes actual configuration of the processor using config
  private val mappings: Map[String, Int] = config.settings("DbProcessor").asInstanceOf[Map[String, Int]]

  override def process(s: String): Int = mappings.getOrElse(s, -1)
}


object ApplicationContext {
  // first create config then pass it explicitly
  val config = new DbConfig(Map[String, Object]("DbProcessor" -> Map("1" -> 123)))
  implicit val p: ProcessorTo[Int] = new DbProcessor(config)
}

Or if you like Cake pattern, you can do something like this:

trait DbConfig {
  def getMappings(): Map[String, Int]
}

class DbProcessor(config: DbConfig) extends ProcessorTo[Int] {
  // here goes actual configuration of the processor using config
  private val mappings: Map[String, Int] = config.getMappings()

  override def process(s: String): Int = mappings.getOrElse(s, -1)
}

trait DbProcessorSupport {
  self: DbConfig =>
  implicit val dbProcessor: ProcessorTo[Int] = new DbProcessor(self)
}

object ApplicationContext extends DbConfig with DbProcessorSupport {
  override def getMappings(): Map[String, Int] = Map("1" -> 123)
}

So the only thing you do in your ApplicationContext is providing actual implementation of the DbConfig trait.

Upvotes: 1

curious
curious

Reputation: 2928

I am a bit confused by the requirements as DbProcessor is missing the process implementation(???) and trait ProcessorTo[T] is missing start method which is defined in DbProcessor. So, I will assume the following while answering: the type class has both process and start methods

Define a type class:

  trait ProcessorTo[T]{
    def start(config: Config): Unit
    def process(s: String): T
  }

Provide implementations for the type class in the companion objects:

object ProcessorTo {
  implicit object DbProcessor extends ProcessorTo[Int] {
    override def start(config: Config): Unit = ???
    override def process(s: String): Int = ???
  }

  implicit object DefaultProcessor extends ProcessorTo[String] {
    override def start(config: Config): Unit = ???
    override def process(s: String): String = s
  }
}

and use it in your ApplicationContext as follows:

  object ApplicationContext {
    def configure[T](config: Config)(implicit ev: ProcessorTo[T]) = ev.start(config)
  }

This is a nice blog post about Type Classes: http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html

Upvotes: 1

Related Questions