Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OnCreate trait: call onCreate method after object creation #4330

Closed
scabug opened this issue Mar 9, 2011 · 18 comments

Comments

Projects
None yet
4 participants
@scabug
Copy link

commented Mar 9, 2011

The DelayedInit trait addresses a rather small niche use case.
The following concerns the problem of calling code (automatically
and implicitly) after an instance has been created but prior to use.
This is useful for frameworks that want to enforce a on-creation
life cycle behavior.

It is always a problem of enforcing a on-creation behaviour
on a class hierarchy. The base class could have code that
gets executed in its constructor:

abstract class Base {
  // ...
  Framework.register(this)
}

but the complete object has not yet been constructed.
The Base class could wrap the code in a method which is called
in the constructor of a derived class

abstract class Base {
  // ...
  def onCreation: Unit=
    Framework.register(this)
}
class Derived extends Base {
  // ...
  onCreation
}

But, the Derived class might be further extended.

The proposed solution is to have a OnCreate trait similar to the
DelayedInit trait:

trait OnCreate {
    def onCreate: Unit
}
abstract class Base extends OnCreate {
  // ...
  def onCreate: Unit = {
    Framework.register(this)
  }
}
class Derived extends Base {
}


// the onCreate method is called after the Derived class is constructed
val d = new Derived

We all know when object construction starts but having code that
executes after an object is created, without being explicit about it,
is not possible.

It is the inclusion of the OnCreate trait that causes the compiler to
generate code that calls the onCreate method after construction
is completed.

// compiler generated code
val d = { val obj = new Derived; obj.onCreate; obj }
@scabug

This comment has been minimized.

Copy link
Author

commented Mar 9, 2011

Imported From: https://issues.scala-lang.org/browse/SI-4330?orig=1
Reporter: Richard Emberson (rmemberson)
See #4680

@scabug

This comment has been minimized.

Copy link
Author

commented Mar 15, 2011

@soc said:
Two points:

  • What about OnInit, OnDestruct, OnChange... where will this start and where will it end?

  • It reminds me of Java's Finalizers which have gone quite wrong. Are there aspects where similar mistakes might be made?

@scabug

This comment has been minimized.

Copy link
Author

commented Mar 15, 2011

Richard Emberson (rmemberson) said:
No difference than DelayedInit but more generally useful.
Assuming one knows when an object has finished
being constructed (its top constructor has
returned) then call a "has-been-constructed"
method - a well defined processing point; really, not like
Finalizers at all.
Currently, one can not register a "construction-completed"
listener that will enforce a behaviour but for
post-construction life cycle activities such as
initialization, change, etc. such behaviours can
be enforced.
How to trigger annotation base instance variable value
injection (e.g, Google Guice), without some means
of knowing when construction has stop but before
usage begins.
Its a means of providing a hook for many on-construction
base behavioural capabilities that can not be
simply and reliably done other ways.
Not thinking about purity here, but rather the building
of enterprise frameworks and attracting Java programmers.

@scabug

This comment has been minimized.

Copy link
Author

commented Mar 15, 2011

@odersky said:
Let's keep the arguments coming. I have taken this on, not as a promise to do it, but to coordinate a discussion of this point.

@scabug

This comment has been minimized.

Copy link
Author

commented Mar 29, 2011

Richard Emberson (rmemberson) said:
The reason for my posting this Bug (#4330) was due to the fact that I
am in the process of porting an enterprise framework written in
Java over to Scala. I am not building on top of the Java version,
I am converting all of it to Java. When the final release for
the Java version made public, I will port the changes since the
previous release candidate and then make the Scale version
of the framework public.

Currently, the Java version is 147kloc (code plus test cases)
while my Scala port is 109kloc (code plus test cases). Porting
the code is not hard. What is hard is finding the one line of
code where I made a mistake during the port - thats the
hard part. One such porting error took 1 1/2 days to find.

In the base class constructor of the frameworks component
hierarchy are two bits of code that are unsafe; code that
executes in the base constructor what communicates with
external code which might expect the instance to be fully
constructed prior to such communication. In both cases,
the creators of the component hierarchy are trying to
enforce a "on-creation" behavior on all users who extend
the class hierarchy.

The first is a such external communication is a call out to
a collection of "on-component-creation" listeners which take
the (partially) constructed instance and, well, do whatever
they want. This is mostly used to integrate with code that
injects values into an object such as the frameworks own
injector, Google Guice, Spring, etc. Since the instance is not
fully constructed when this happens, I have to be careful that
I do not actually, explicitly set the value of a field in a
derived class constructor because that will override the
injected value. (I have to define the instance variable as,
e.g.: "var value: String = _", which will not override the
injected value, but expecting end users to do this correctly
is problematic at best.)

The second external communication in the constructor is that
the instance can be passed to an object passed in as constructor
parameter. End users can create their own versions of such a
parameter object and they may have expectations that the object
being constructed is fully constructed prior to it being passed
to their parameter object:

    class Node(data: Data) {
        if (data.instanceOf[WrappedData])
            data.asInstanceof[WrappedData].setNode(this)
    }

The end user will have difficulty determining why things are not
working where the answer is the fact that the instance is not
fully constructed yet. For example, calling an instance method
which is override in the not-yet constructed derived class.

So, to make the framework safer while providing the ability to
enforce a number of "on-construction" behaviors is the reason
I posted the bug.

@scabug

This comment has been minimized.

Copy link
Author

commented Apr 11, 2011

@odersky said:
I agree it's a nice to have functionality which needs to be balanced against the increased complexity and language footprint if we were to implement something like that. Right now I do not see it's worth it. What in onCreate is Scala specific? If it makes sense to add it to Scala why has it not been added to Java? Generally, we strive to be leaner and more regular than Java in these things.

So why is DelayedInit different? Two reasons: First, DelayedInit does have to do with Scala's functional nature. You cannot simulate DelayedInit yourself without changing all affected val fields to vars. Second, DelayedInit fixes a very common pattern with the Application trait.

@scabug

This comment has been minimized.

Copy link
Author

commented Apr 11, 2011

Richard Emberson (rmemberson) said:
Of course, I must respect your decision.
Where will Scala converts some from? Wait a generation for
new programmers to be trained or from existing Java
programmers. I have always argued that Scala is a better Java;
setting aside Scala's functional half, its just a better Java.
Now, in the case of post-construction behavior, Scala
will be just like Java, error prone at best.
I do not believe that the limitations of Java should
be the measure for Scala:
"If it makes sense to add it to Scala why has it not been added to Java?"
Oh well ...

@scabug

This comment has been minimized.

Copy link
Author

commented Jun 7, 2011

Richard Emberson (rmemberson) said (edited on Jun 7, 2011 6:16:07 PM UTC):
It appears the the following approach might get the "onCreate" functionality
on a case-by-case basis:

trait A extends DelayedInit {
  println("A ConstructionCode")

  def delayedInit(body: => Unit) = {
    body
    postConstructionCode
  }
  protected def postConstructionCode: Unit = {
    println("A PostConstructionCode")
  }
}
trait B extends A {
  println("B ConstructionCode")
  override protected def postConstructionCode: Unit = {
    super.postConstructionCode
    println("B PostConstructionCode")
  } 
} 
  
class C extends B { 
  println("C ConstructionCode")
  override protected def postConstructionCode: Unit = {
    super.postConstructionCode
    println("C PostConstructionCode")
  }
}object Test {
  def main(args: Array[String])
    val c = new C
  }
}

This outputs:

A ConstructionCode
B ConstructionCode
C ConstructionCode
A PostConstructionCode
B PostConstructionCode
C PostConstructionCode

5W (which was what we wanted)!

Of course, each framework would have to create their own "onCreate"
method (called "postConstructionCode" in this example) and users would have
to make sure that the protected method was not normally called - a call once
methods.

@scabug

This comment has been minimized.

Copy link
Author

commented Sep 30, 2012

Erik Westra (eecolor) said:
I have a problem that I can not solve easily. I am experimenting with a user interface library that can have different native implementations.

A Window for example might be an OS window, same with Stage. So when a Window instance is created I want to call the underlying native engine to create the appropriate object for that component.

I tried it with the code below, but the delayedInit method is called twice when an instance of Stage is created, once for the body of Window and once for the body of Stage. As far as I can tell there is no way to tell from within the Native trait if I am executing the last body.

trait Native extends DelayedInit {
  def delayedInit(body: => Unit) = {
    body
    println("object created")
    //create native object for this 
  }
}

class Window extends Native {

  val windowProperty: Boolean = false
}

class Stage extends Window {

  val stageProperty: Boolean = false
}

I see two workarounds:

  1. Traversing all of the components created in a hierarchy and creating their native counterparts if needed
  2. Let all the components that are native override a method that does exactly the same thing: create native object for this

Both of these workarounds are problematic: 1. is bad for performance and 2. goes against the DRY principle.

This would be easily solved by a OnCreate trait as suggested.

Can this problem be solved without the OnCreate trait?

@scabug

This comment has been minimized.

Copy link
Author

commented Oct 21, 2012

Erik Westra (eecolor) said (edited on Oct 21, 2012 8:43:43 PM UTC):
I found a workaround, this however is not ideal because it relies on implementation specific details of Scala:

trait OnCreate extends DelayedInit {
  def onCreate:Unit
  
  def delayedInit(body: => Unit) = {
    body
    
    if ((body _).getClass.getDeclaringClass == this.getClass)
    {
      onCreate
    }
  }
}

class A extends OnCreate {
  def onCreate = println("Creation is fully complete")
  val x = ""
  println("a complete")
}

class B extends A {
  val y = ""
  println("b complete")
}

Executing new B results in:

a complete
b complete
Creation is fully complete
@scabug

This comment has been minimized.

Copy link
Author

commented Mar 15, 2013

@adriaanm said:
Un-assigning to foster work stealing, as announced in https://groups.google.com/forum/?fromgroups=#!topic/scala-internals/o8WG4plpNkw

@scabug

This comment has been minimized.

Copy link
Author

commented Jul 10, 2013

@adriaanm said:
Unassigning and rescheduling to M6 as previous deadline was missed.

@scabug

This comment has been minimized.

Copy link
Author

commented Jan 28, 2014

@adriaanm said:
I like the simplicity of this proposal. It also provides a hook for reducing constructor macros to normal def macros.
It's a lot simpler than DelayedInit, though I don't see how it lets us eliminate the main boilerplate (but that's okay -- maybe we can use top-level defs for that).

I'd propose considering how this relates to language virtualization and constructor macros.
Would be nice to use the former to subsume the latter with regular macros.

trait OnCreate[+T] { self: T =>
    // Don't actually include this method signature, as we cannot express that we want
    // to allow a subclass implementation to add an implicit arg section or that it could be a macro
    // def completeConstruction: T
}

for any C <: OnCreate[C], we rewrite new C to (new C).completeConstruction in the same style as for comprehension desugaring/language virtualisation.

macro example:

scala> trait OnCreate[T] { self: T => } //def completeConstruction: Any }
defined trait OnCreate

scala> def impl[T: c.WeakTypeTag](c: BlackboxContext): c.Tree = { import c.universe._    
     |     println(typeTag[C])
     |     q"""???"""
     | }    
impl: [T](c: scala.reflect.macros.BlackboxContext)(implicit evidence$1: c.WeakTypeTag[T])c.Tree

scala> class C extends OnCreate[C] { import language.experimental.macros
     |   println("c")
     |   def completeConstruction = macro impl[C] 
     | }
defined class C

scala> (new C).completeConstruction
TypeTag[C]
scala.NotImplementedError: an implementation is missing
  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:229)
  ... 32 elided

scala> (new C)
c
@scabug

This comment has been minimized.

Copy link
Author

commented Sep 22, 2014

James Diebel (jamesd8) said:
I would definitely like this feature.

@scabug

This comment has been minimized.

Copy link
Author

commented Feb 14, 2016

Markus Marvell (Marvell) said (edited on Feb 14, 2016 10:04:39 AM UTC):
Such feature would allow development of more advanced and sophisticated frameworks.
Definitely required.

@scabug

This comment has been minimized.

Copy link
Author

commented Feb 16, 2016

Markus Marvell (Marvell) said (edited on Feb 21, 2016 9:59:31 AM UTC):
The idea behind DelayedInit is great. It has one advantage over suggested OnCreate. It calls each superclass constructor body on the way to leaf subclass. For example we can design a safe object that doesn't break execution when constructor (one of in hierarchy) throws an exception. Instead it encapsulates a state that can be queried before using it.

trait Worker {
	def use(): Unit
}

trait SafeWorker extends DelayedInit with Worker{
	private[this] var ok = true
	override final def delayedInit(body:  Unit): Unit = {
		if (ok) try body catch {case e: Throwable  ok = false}
	}
	def isOk: Boolean = ok
	def use(): Unit = if (isOk) println(s"Working...") else println(s"Oops...")
}

class MediaSafeWorker extends SafeWorker {
	throw new Exception
}

class MySafeWorker extends MediaSafeWorker { () }

class UnsafeWorker extends Worker{
	throw new Exception
	def use(): Unit = println(s"Working...")
}

Running this code prints: "Oops..."

object ATest extends App {
	val safeObj = new MySafeWorker
	safeObj.use()
}

But running this code breaks execution with exception.

object ATest extends App {
	val unsafeObj = new UnsafeWorker
	unsafeObj.use()
}

(i) SUGGESTED OPTION

DelayedInit trait needs some improvements.

  • It skips empty constructor body as mentioned in SI-4680. It would be great if it will pass to delayedInit method even empty body.
  • @erik Westra suggested [brilliant idea|(https://issues.scala-lang.org/browse/SI-4330?focusedCommentId=60542&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-60542) to detect class of currently executing constructor body. And it would be great to have delayedInit method version with extra argument for explicit current body class.
def delayedInit (body: => Unit, bodyClass: Class[_])

. Or even version with argument for explicit mark indicating leaf class body.

def delayedInit (body: => Unit, bodyClass: Class[_], isLeafClass: Boolean)

Or add def onCreate() when object construction is complete.

(!) NOTE for Android developers.
Seems better add this line in Proguard config to avoid java.lang.NoClassDefFoundError:

-keep class scala.DelayedInit
@scabug

This comment has been minimized.

Copy link
Author

commented Apr 20, 2016

Charles Papon (Dolu) said (edited on Apr 20, 2016 3:40:38 PM UTC):
Having a callback after the class construciton is very usefull for some internal DSL.
In my current project, i have to keep kind of "execution context" and some "hierarchy context".

Of course the user can push/pop things on the context stack by himself, but the goal is to have a smooth internal DSL that look like a proper language.
(the pop is currently done by DelayedInit, the push is currently done by the superclass's constructor)

@SethTisue

This comment has been minimized.

Copy link
Member

commented Mar 2, 2018

could be revived as a discussion on https://contributor.scala-lang.org

@SethTisue SethTisue closed this Mar 2, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.