Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delaying trait initialization

I need a smart mechanism for component composition which allows mixed in traits to initialize after the composed component. The following throws a NullPointerException:

class Component {
  def addListener(pf: PartialFunction[Any, Unit]) {}
}

trait DynamicComponent {
  protected def component: Component

  component.addListener {
    case x =>
  }
}

class Foo extends DynamicComponent {
  protected val component = new Component
}

new Foo  // -> NullPointerException

The following things are not options for me:

  • Using protected lazy val component; that would produce an avalange of dozens of vals needing to become lazy, something I do not want.
  • Putting addListener in a method, e.g. initDynamic(); because I will be mixing in many traits, and I don't want to remember to call half a dozen initFoo() methods.
  • Using DelayedInit. This doesn't work with traits, at least according to the scaladocs.

I could live with a single init() call, but only under the following conditions:

  • all mixed in traits can easily declare to be invoked in this one single call
  • it is a compile error to forget the init() statement.
like image 928
0__ Avatar asked Apr 27 '13 11:04

0__


3 Answers

You can delay the initialization of a trait by by using early definitions. (See section 5.1.6 of the scala language specification)

class Foo extends {
  protected val component = new Component
} with DynamicComponent
like image 55
Martin Ring Avatar answered Nov 06 '22 22:11

Martin Ring


It's even clunkier than your solution, but you can always require the creation of a val that must be set with the init() method. You could choose to not do it last and get an error at runtime, but at least you won't forget it entirely:

class Component {
  def addListener(pf: PartialFunction[Any, Unit]) {
    println("Added")
  }
}

trait Dyn {
  protected def component: Component
  protected val initialized: Init
  class Init private () {}
  private object Init { def apply() = new Init() }
  def init() = { component.addListener{ case x => }; Init() }
}

class Foo extends Dyn {
  protected val component = new Component
  protected val initialized = init()
}

No cheating!:

> class Bar extends Dyn { protected val component = new Component }
<console>:12: error: class Bar needs to be abstract, since value
initialized in trait Dyn of type Bar.this.Init is not defined
       class Bar extends Dyn { protected val component = new Component }

The advantage this has is if you need multiple things to be in place before you initialize all of them cooperatively, or if your Component class is final so you can't mix in anything else.

like image 26
Rex Kerr Avatar answered Nov 07 '22 00:11

Rex Kerr


AN idea could be to use the trick described here: Cake pattern: how to get all objects of type UserService provided by components

All your components that should be initialized could be registered in some Seq[InitializableComponent]. And then you could initialize all registered components with a foreach.

No component will be forgotten in that Seq because they are registered automatically, but you can still forget to call the foreach anyway...

like image 1
Sebastien Lorber Avatar answered Nov 06 '22 22:11

Sebastien Lorber