简体   繁体   中英

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?

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM