Scala 3: DelayedInit

Hi all,

I would like to trigger a discussion about the planned removal of DelayedInit in Scala 3.

specs2 has been using DelayedInit almost since its inception as a way to create a Scope for a given test:

    class ContextSpec extends mutable.Specification {
      "this is the first example" in new directories {
        directory1.files must beEmpty
        directory2.files must not(beEmpty)
      }
    }

    trait directories extends Scope {
      val directory1 = createEmptyDirectory
      val directory2 = createDirectoryWithFiles("f1.txt", "f2.txt")
    }

This allows users to define a bunch of variables (directory1, directory2, which are initialized first then accessed from the body of a trait. The alternative using Scala 3 would be to use specs2 ForEach:

    class ContextSpec extends mutable.Specification with FileSystem {
      "this is the first example" in { directories:  Directories =>
        import directories._
        directory1.files must beEmpty
        directory2.files must not(beEmpty)
      }
    }

   case class Directories(directory1: Directory, directory2: Directory)

   trait FileSystem extends ForEach[Directories] {
     def foreach[R: AsResult](f: Directories => R): Result = 
         f(createDirectories)
   }

This other version is a bit more verbose but my main concern is all the code that is out there using Scope and DelayedInit under the covers. Also it could be argued that it is in general useful to intercept the execution of code in the body of a class. This enables better error messages when a class is instantiated through reflection if it throws an exception. We can then intercept that exception and report a better error message than NoClassDefFoundError, maybe something like “you should probably use lazy vals :-)”.

2 Likes

Sorry for adding a “me too” type of post, but we’re using this API heavily at Wix, without a clear migration path it would be very painful to replace.

Didn’t you notice the existing thread on this subject?

I would imagine that discourse even suggested it to you as you typed in the subject.

Could you clarify which API you’re referring to? The Before/After/Around hooks that use DelayedInit, or just Scopes – the core feature is not affected AFAICT.

All of the above, to be honest. We use specs2 for integration/end-to-end tests and we use those Before/After hooks to start/stop services, as well as Context objects to initialize service-specific stuff.

Just today a colleague wanted to extend a bit of API in specs, and turned out the feature he needed was the DelayedInit ability…

Hi etorreborre, I’m wondering what role DelayedInit is playing in your example. I searched https://github.com/etorreborre/specs2 for DelayedInit and found it in Before and After. Is it possible to alter your test runtime to explicitly call before and after instead of relying on compiler rewriting behavior?