DelayedInit or OnCreate, any solution?


#1

DelayedInit is deprecated in favor a long-pending OnCreate:

However, the ticket was recently closed (along with all DelayedInit tickets), which begs the question: what now?


Scala 3: DelayedInit
#2

I don’t have a solution yet. Before checking what one could/should do, it would be good to have some use cases. Where do people use DelayedInit (other than for App)?


#3

The other one I know of is ScalaFX and the scala.application.JFXApp. Clearly, it is a similar usage to App, but I have a feeling that there might be differences in the details. JavaFX has some quirks in how applications are started that might cause JFXApp to be a bit different than App.


#4

There’s one use of DelayedInit in ScalaTest:

http://doc.scalatest.org/3.0.0/index.html#org.scalatest.fixture.NoArg

Bill


#5

Specs2 has at least one, and it has caused me trouble a couple times. It tends to get new users who implement Scopes with abstract classes rather than traits, and get a surprise double-call or unexpected order.

A couple examples of confused users:



#6

Forgive me if this is a stupid idea, but maybe we can use implicits for this? If we have a special typeclass:

trait OnCreate[T : Classtag] {
  def apply(t : T) : T 
}
class Foo
trait FooInit extends OnCreate[Foo] {
  def apply(t : T) : T = ???
}
object FooInit {
  implicit def ev : FooInit = new FooInit {}
}

We only need the compiler to translate
val foo = new Foo{}
into
val fooInit = implicitly[FooInit]; val foo = fooInit(new Foo{})


By using implicit we can also avoid mutating Foo, but just create an initialized copy of it.


#7

Well I use it (as well as others) for DSL purposes to populate name tags for specific objects by using Java runtime reflection. E.g:

trait Foo {
  val m1 : MySpecialType
  val m2 : MySpecialType
}

I want to tag the m1 and m2 objects with their names "m1" and "m2" respectively.


#8

Could that use case be satisfied by sourcecode.Name from https://github.com/lihaoyi/sourcecode?


#9

Very cool lib. I’ll try it out!


#10

Just to expand on the use case in ScalaFX, the scalafx.application.JFXApp trait primarily does two things:

  1. Firstly, it initializes JavaFX and starts the JavaFX Application Thread (JFXAT), using any supplied command line arguments.
  2. It then uses scala.DelayedInit to queue up all sub-class constructors (in the exact same manner as with scala.App) but then passes them for execution to the JFXAT.

This is extremely convenient, because interaction with the JavaFX API must typically take place only on the JFXAT. This allows ScalaFX users to write trivial GUI applications in their application constructors, safe in the knowledge that the JFXAT will have been created and initialized, and that those constructors execute on that thread too.

For example:

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.scene.Scene
import scalafx.scene.paint.Color._
import scalafx.scene.shape.Rectangle

object HelloStageDemo
extends JFXApp {
  stage = new JFXApp.PrimaryStage {
    title.value = "Hello World!"
    width = 600
    height = 450
    scene = new Scene {
      fill = LightGreen
      content = new Rectangle {
        x = 25
        y = 40
        width = 100
        height = 100
        fill <== when(hover) choose Green otherwise Red
      }
    }
  }
}

Without DelayedInit, all bets are off. Even onCreate doesn’t appear to address how this can be resolved. The only solution appears to be to put what used to be in the constructor into a virtual function, and hope that no-one attempts to interact with JavaFX from an application constructor. This a far less elegant, and would break all existing user code too.


#11

I would like to add my own use-case for DelayedInit, perhaps contributing to the argument that it used in more cases than just a few niche ones. In my use case I’m trying to add an ability to chain test cases written with ScalaTest. I’ll let the code explain it:

import com.typesafe.scalalogging.StrictLogging
import org.scalatest._

import scala.collection.mutable

/**
  * A mixin (fun) suite which introduces the concept of chained tests; a chained test will be first executed like any
  * other test in the suite, but after all the "normal" executions are done, the chained test will be executed in pairs
  * with each of the other chained tests in the suite (including itself).
  *
  * For instance, if the suite has 2 "normal" tests (1, 2) and 3 chained tests (3, 4, 5), then first all the tests will
  * be executed normally (1, 2, 3, 4, 5). Afterwards, each pair possible to construct from the chained tests will be
  * executed as an individual test; a test of 3 and then 3, a test of 3 and then 4, 3 and then 5, 4 and then 3, etc
  * (total of 3 * 3 = 9 additional tests).
  *
  * This is especially useful when testing units which are stateful by their nature, since it is vital to assert that
  * the unit is capable of executing separate use-cases in succession without breaking its inner state.
  */
trait ChainedTestsSuite extends DelayedInit with StrictLogging {
  self: FunSuiteLike =>

  /* --- Data Members --- */

  private val tests = mutable.Buffer.empty[ChainedTest]

  /* --- Methods --- */

  /* --- Public Methods --- */

  override def delayedInit(suiteBody: => Unit): Unit = {

    suiteBody

    for (testA <- tests; testB <- tests) {
      test(s"Chain test: ${testA.order} and then ${testB.order}") {
        logger.info(s"Executing first test: ${testA.name}")
        testA.testFun()
        betweenChainedTests()
        logger.info(s"Executing second test: ${testB.name}")
        testB.testFun()
      }
    }
  }

  /**
    * Override this in order to execute code between two chained tests.
    */
  def betweenChainedTests(): Unit = {}

  /* --- Protected Methods --- */

  /**
    * Registers a test as a chained test.
    *
    * @see [[FunSuiteLike.test]]
    */
  protected def chainTest(testName: String, testTags: Tag*)(testFun: => Unit): Unit = {

    val order = tests.size + 1
    val chainedName = s"($order) $testName"

    test(chainedName, testTags: _*)(testFun)
    tests += ChainedTest(chainedName, order, () => testFun)
  }
}

object ChainedTestsSuite {

  /* --- Inner Classes --- */

  private case class ChainedTest(name: String, order: Int, testFun: () => Unit)

}

I’m guessing this is an ability that could be added to ScalaTest without the need to use DelayedInit, but for someone who wants to “act quickly” this is sometimes a viable feature of the language.


#12

I take the chance to add another use case. We have test classes that implement certain life-cycle methods. In delayedInit the life-cycle methods are called in proper sequence including the actual test code:

**
  * Base trait for tests that takes care that the life-cycle methods of a test are called in proper sequence.
  *
  * Required components can be mixed into this trait. Read the documentation of the [[WithLifecycle]] trait
  * carefully in order to implement proper startup and shutdown sequences.
  */
trait InnerTest extends WithLifecycle with WithLog with DelayedInit {

  override def delayedInit(testCode: => Unit): Unit = {

    tryFinally {
      log.debug(s"before inner test - class: ${getClass.getSimpleName}")
      startup()
      log.debug(s"execute inner test - class: ${getClass.getSimpleName}")
      testCode
      log.debug(s"shutdown inner test - class: ${getClass.getSimpleName}")
      shutdown()
      log.debug(s"wait for completion of inner test - class: ${getClass.getSimpleName}")
      waitForCompletion()
      log.debug(s"inner test completed regularly - class: ${getClass.getSimpleName}")
    } {
      log.debug(s"cleanup inner test - class: ${getClass.getSimpleName}")
      cleanup()
      log.debug(s"after inner test - class: ${getClass.getSimpleName}")
    }
  }

}

#13

We also use this type of dsl. I think with static scope injection and implicit function type (


)
It can be done better.