1

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?

7
  • 2
    Why not require that any method which wants to do processing will have an implicit ev: Processor[T]? What is this ApplicationContext object? Commented Nov 22, 2017 at 13:03
  • @YuvalItzchakov ApplicationContext object contains typeclasses needed to be imported. Commented Nov 22, 2017 at 13:05
  • @YuvalItzchakov Even if I require implict ev: Processor[T], I need to configure the DbProcessor anyway. Commented Nov 22, 2017 at 13:06
  • Ok, do you have to use an object to place all these implicits there? I'd separate these concerns out and have the outmost top place create an instance of the right processor I need. Commented Nov 22, 2017 at 13:07
  • @YuvalItzchakov The reason I put all these implicits there was the following: I wanted to generify my processing engine which using some of these implicits and to modify its behavior just import the configured application context. So I abstraced away constructurs and configuration of the specific implicit in my application. Commented Nov 22, 2017 at 13:12

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

Comments

1

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.