Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheriting self-typed trait with another trait in Scala

Tags:

scala

I'm trying to design a small module system for my application such that I can do this:

new MyApplication extends Module1 with Module2 ... with ModuleN

In order to let my modules register themselves with the application, I also have:

trait ModuleRegistry {
  def register(start: () => Unit) = // Stores start functions in a list
}

trait Module {
  self: ModuleRegistry =>
  self.register(start)
  def start(): Unit
}

class Application extends ModuleRegistry {
}

trait Module1 extends Module {
  ...
}

The idea is that modules can register a function with the registry to be called when the application starts up. Unfortunately, the Scala compiler forces me to do this:

trait Module1 extends Module {
  self: ModuleRegistry =>
}

meaning that all implementations of a module have to explicitly self-type themselves with the registry, when ideally they'd have no knowledge of it.

So my questions are:

  1. Why does the compiler force me to re-specify this self-type on an extending trait? Ideally I don't even want the extending trait to 'see' this type, as it should be handled by the base Module trait. The 'concrete' module is meant to be implemented by 3rd parties, so exposing the registry to them seems kinda ugly.
  2. Is there a better way to do what I'm trying to achieve in Scala so that modules can be freely mixed in?
like image 439
shanloid Avatar asked Jul 19 '15 21:07

shanloid


1 Answers

This isn't a good use of the cake pattern, as you want all the modules to retain their own functionality (presumably), not mix and overwrite each other when mixed in. If you want the latter, you shouldn't: only with very careful design can this give predictable and sane results, and if third parties are supposed to provide the modules it's pretty much guaranteed that the design will not be that careful.

Instead, you should adhere to the "favor composition over inheritance" advice.

trait Module {
  def start: Unit
}

trait Modular {
  protected def moduleList: Seq[Module]
  protected def register() = moduleList.map(m => m.start _)
}

class Application(modules: Module*) extends Modular {
  protected def moduleList: Seq[Module] = modules  // Fix varargs type
  protected val registered = register()
}

object Module1 extends Module {
  def start = println("One")
}

object Module2 extends Module {
  def start = println("Two")
}

val app = new Application(Module1, Module2) {
 registered.foreach(_())
}
// Prints "One", then "Two"

This is assuming a singleton is okay for a module. If you need a specific instance, you can either override apply (syntax is then Module1()) in the companion object, or add a builder trait that Module1 extends (e.g. trait ModuleBuilder { def module: Module }).

like image 137
Rex Kerr Avatar answered Oct 05 '22 22:10

Rex Kerr