DelayedInit, what is the alternative?

Sorry for bringing this up again, but i believe there is still no solution? As of version 3.3 DelayedInit which is deprecated at this moment, is dropped as it seems? In all of the dropped features discussed there, some kind of alternative is given, but for DelayedInit we are left in the dark.

Now the problem of actions after object creation were described most clearly in the first post by scabug in the discussion OnCreate trait: call onCreate method after object creation #4330 in 2011. The solution seemed as clear to me. But then it was decided to introduce DelayedInit, which is now phased out. Subsequently it was discussed in the thread DelayedInit or OnCreate, any solution? started in 2018 by soronpo. A lot of use cases were presented, but no action was formulated.

Fast forward 5 years. Recently i stumbled on the problem myself: Having this library (scastie: super minimised):

abstract class Book :
  def title: String 
  def content: String
  /* Register each book at construction. */
  Store.add(this)

object Store :
  private var shelf: Map[String,Book] = Map.empty
  def add(book: Book): Unit = shelf += book.title -> book
  def list: Iterable[String] = shelf.keys 

and the library user may then write

class PizzaBook(val owner: String) extends Book :
  val title = "About Pizzas"
  val content= "Start with some wheat ..."

but adding the book this way is not going to work, as null as title is stored

val b1 = PizzaBook("Jan")

Of course, one can demand the user to register manually,

Store.add(b1)

but this can easily be forgotten.

You may also demand the library user to define the title as class parameter

class CarBook(val title: String, val owner: String) extends Book :
  val content= "The Ferrari ..."

so that one uses

val b2 = CarBook("My Ferrari","Piet")

but this cannot be enforced other then by adding the title parameter to Book itself, and this
is not favourable in a lot of use cases.

Now, what is the magic trick that can be performed?

In your example, it seems the class Book could take parameters:

abstract class Book(val title: String, val content: String):
  /* Register each book at construction. */
  Store.add(this)

This would ensure that the title and content are initialized before Store.add(this) is evaluated.

2 Likes

In this case PizzaBook can mark the title as final or lazy and it seems to resolve properly. but perhaps that’s not ideal for your given use case.

2 Likes

FP-oriented folks probably have a word for this issue, which is when do you force something constructed lazily?

The OOP answer, that objects must be completed at the end of construction (and not leaked ahead of time) is unsatisfying. There may be many dependencies required but which aren’t computed yet. Also maybe they will never be needed. Why precompute the world?

This version of the code uses a dirty bit to recompute on access, a minimal naive approach.

This version also normalizes the colons. I was also excited to write println:, using the latest dotty locally.

abstract class Book:
  def title: String
  def content: String
  // Register each book at construction.
  Store.add(this)

object Store:
  private var shelf: Map[String,Book] = Map.empty
  private var dirty = false
  private var added: List[Book] = Nil
  def add(book: Book): Unit =
    added ::= book
    dirty = true
  def list: Iterable[String] =
    if dirty then
      shelf = added.map(bk => (bk.title, bk)).toMap
      dirty = false
    shelf.keys

case class PizzaBook(owner: String) extends Book:
  val title = "About Pizzas"
  val content= "Start with some wheat ..."

@main def test() = println:
  val book = PizzaBook("Bob")
  (book, Store.list.toList)
1 Like

This would ensure that the title and content are initialized before Store.add(this) is evaluated.

True, but is would also require the user to define those as class parameters too, as I stated in my original post. A restriction i do not want to impose upon the user just yet. (And which disables to possibility to get the name from a method inside the class)

In this case PizzaBook can mark the title as final or lazy and it seems to resolve properly. but perhaps that’s not ideal for your given use case.

Yes, also possible. But this forces the user to use final val, and when it is forgottten, we have a null appearing in the Store … brrr! This solution would be good if it is somehow possible to force the user to use the keyword final on the definition of that name. I am not aware of such a method, but if there is one, this would certainly be the solution.

This version of the code uses a dirty bit to recompute on access, a minimal naive approach.

Nice, i came up with a variant of this idea. And maybe this is the only alternative just now. But to be honest, my post was not about this specific code. If i wanted a solution, I would have posted it in users.scala-lang.org. It was just an example to show the problem.

What i really want is a discussion about a removed feature of the language without an alternative being presented. And i don’t think this is an obscure feature, given the fact that it was already discussed in two lengthy threads.

My point, if I may try to make it again, is that the feature was a non-feature.

For some reason, lazy val is uncontroversially a feature.

For some reason, lazy parameter def f(lazy x: => Int) is not.

One may say that not all modes of computation are supported as “opaque” language features.

In that sense, I would dispute “a removed feature of the language without an alternative being presented”.

It’s not the case that there is no way to express this in the language.

To paraphrase the Klangism, what are you trying to express? If it is dubious from an architectural perspective, then first class support is especially unwarranted.

To state the obvious, Scala has experimented or shall we say dabbled in features which did not pan out.

In addition, you’re asking for a discussion on the basis that the topic has already been discussed at length. It can be useful to revive old threads, and certainly old bug reports that were once intractable may be fixable or already fixed because of subsequent work, but as in courts of law, it requires the introduction of new evidence which might result in a new outcome.

1 Like

I agree that it isn’t obscure, but whether it matters or not depends a lot on your coding style. (Eg, I think I’ve used DelayedInit once in 15 years of using Scala.)

I mean, I appreciate that you firmly don’t want parameters on the base Book class, but I think I would always do it that way. Especially in Scala 3, which lets you have constructor params on traits, which I vaguely recall were specifically considered the alternative allowing DelayedInit to be deprecated.

1 Like

Thank you for trying to see my point of view. And i am following your advice. If parameters are the way to go, the user of the library must accept this is how it works, despite the fact that maybe (s)he is forced to carry the parameters along in each derivation of the base trait/class. In the mean time i also found a workaround for automagic naming.

I think it would be good that anytime some feature is removed from the language, some guidance is given what to replace it with, even if that is “just” an other coding style. Usually this is also the case (see most dropped features). But not so for Delayed Init, which was the primary cause for posting this.

1 Like