Alternative to init{} block which executes after Object instantiation

The init{} block has its flaws. It cannot guarantee the null safety of abstract fields. It also cannot guarantee that the value of an open field is what its final value will be, as it could get overwritten multiple times.

I propose the instance{} block. This block will run after the object is fully instantiated. This will guarantee that the abstract fields in the Object are not null, and that the open fields have the latest overwritten value.

Given: a Var is a class which resets its value when another Value<*> which it depends on resets its value. Calling getValue() lazily computes the value if it has been reset.

    abstract class BoundingBox {
        
        abstract val x: Var<Float>
        abstract val y: Var<Float>
        open val width: Var<Float> = Var { 0f }
        open val height: Var<Float> = Var { 0f }
        val bounds: Var<Rectangle> = Var { 
            Rectangle(
                x.getValue(), 
                y.getValue(), 
                width.getValue(), 
                height.getValue()
            ) 
        }
        
        init {
            bounds.dependsOn(x, y, width, height)
        }
    }

BoundingBox wants to define that whatever x, y, width, height values eventually get assigned to it will trigger the reset of the bounds Var. This is impossible using init{} as it will throw NPE for x and y, and will operate on the default non-overridden value of width and height.

To solve this, you could require the abstract fields to be passed in as params, but if you have many fields like this, it would become cluttered. Open fields need more confusing logic. The whole thing very user-unfriendly.

Another solution would be to define a function onCreate() which could have the logic contained by init{} block in above example. Problem here is that the user must implement this each time, and there’s no guarantee they will. Or they may call it multiple times, or not immediately after creation, creating an object state that cannot be guaranteed.

Having an instance{} block whose logic can be defined in the original abstract object and then implemented by the final extension of that object solves these problems in a clean, reliable, and enforceable way.

What is your use case?

Constructors are mostly to initialize properties. So assuming you want to initialize props in your instance{}, what if you like to run some code after instance{}? Do we need a third round of constructors?

2 Likes

Updated original post.

For multiple rounds of instance{}, you could do it like for multiple rounds of init{}. If A is implemented by B and they both have init{}, then A’s init{} runs first, and then B’s runs. The same could be done for instance{}. Further, if B needs 2 rounds of instance{}, then its instance{} could run two functions: firstRound() and secondRound(). These can be open, and the user could implement them as needed. There’s a lot you could do with a platform like instance{}.

I meant something different by saying “rounds”. But your example is actually good to explain the problem.

What if we need to create another property foo that references bounds? We need another round of initialization, executed after instance{}. And if we have bar referencing foo, we need yet another, fourth round of initialization. And so on.

You said that instance {} should be executed when it is guaranteed that all properties are already initialized. At the same time you want to postpone initialization of bounds into instance {}. Doesn’t the second contradict the first?

No, we’re not initializing bounds, or any properties in the instance{} block. We’re simply applying some logic to them, which is why it’s not appropriate to do so in init{} block. My intention with instance{} is to apply some post-processing logic to the object once all its properties are initialized.

Yes, I understand this. And my question is: what if you then need to add a post-processing logic to another class that needs to be done after the post-processing of BoundingBox? I feel like we don’t really solve this problem, but only move it one level further.

The instance{} block just executes after the object is created. Exactly like doing:

val boundingBox = object: BoundingBox(){...}
boundingBox.instance()

So execution order of instance{} would be determined by object instantiation.

If there’s another object which references my boundingBox val, then it must have been instanced after boundingBox, and therefore after boundingBox’s instance{} block.

And if Var<*> class had an instance{} block defined, those would execute when those fields are created as well. So in this case, when we create a BoundingBox, the instance{} order would be:

x instance{}
y instance{}
width instance{}
height instance{}
bounds instance{}
boundingBox instance{}

As an afterthought, let’s say that Var must be initialized with a BoundingBox object. So they would be created like this:

val x: Var<Float> = Var(this) { 0f }

In that case, accessing the BoundingBox in a Var’s instance{} block is actually equivalent to accessing the BoundingBox in the BoundingBox’s own init{} block, since properties may not have been fully overridden, or instantiated at that point. But it’s generally bad practice to pass this as parameter to an object that’s a property of this. There’s a Kotlin warning in IntelliJ to encourage avoiding doing exactly that, for similar reasons.

Well, I think I missed one thing here: that you probably don’t necessarily need x and y to be fully initialized at the time you run bounds.dependsOn(). You only need x and y to exist as objects. In this case such instance {} could actually work and solve your problem.

You would need them to be fully instanced because you’re passing them in as parameters:

bounds.dependsOn(x, y, width, height)

And they are fully instance at this point since instance{} of BoundingBox runs after x, y, width, height, and bounds are all created.

The instance{} block is almost like doing

override val x = Var { 0f }.apply { /* do stuff */ }

Let’s create InfiniteBoundingBox class:

    class InfiniteBoundingBox: BoundingBox {

        override val x: Var<Float> = Var { Float.NEGATIVE_INFINITY }
        override val y: Var<Float> = Var { Float.NEGATIVE_INFINITY }
        override val width: Var<Float> = Var { Float.POSITIVE_INFINITY }
        override val height: Var<Float> = Var { Float.POSITIVE_INFINITY }
    }

And let’s also say that BoundingBox has this instance{} block defined:

        instance {
            bounds.dependsOn(x, y, width, height)
        }

In this case, our instance{} block will pass in the newly overridden fields of x, y, width, height as parameters in the instance{} defined by BoundingBox which it extends. Because width and height properties are open, and have default value, I believe Kotlin creates them in BoundingBox, but then replaces them with the overridden values of InfiniteBoundingBox. At the point of BoundingBox instance{}, they have already been overridden to the new infinite ones defined by InfiniteBoundingBox.

Hopefully this helps.

And this is exactly what I believe is impossible to do. Ok, let’s do this with the code:

Case 1:

class InfiniteBoundingBox: BoundingBox() {
    ...

    val foo = Var {
        bounds.getValue()
    }

    instance {
        foo.dependsOn(bounds)
    }
}

Case 2:

abstract class SuperclassOfBoundingBox {
    abstract val bounds: Var<Rectangle>

    val bar = Var {
        bounds.getValue()
    }

    instance {
        bar.dependsOn(bounds)
    }
}

How do you want to handle both these cases? What is the order of running instance {} of SuperclassOfBoundingBox, BoundingBox and InfiniteBoundingBox?

We can ensure that at the time any instance {} block is executed, all Var properties already exist. But we can’t ensure that we always bound to variables that were itself fully initialized and “know” their value.

You could treat instance{} as an open function which all Objects have:

open fun instance() {}

When a class defines an instance{}, it could be considered the same as doing this:

override fun instance() {
    super.instance()
    // do some new stuff
}

So SuperclassOfBoundingBox appends bar.dependsOn(bounds) to that function. Then BoundingBox appends bounds.dependsOn(x, y, width, height), and finally InfiniteBoundingBox appends foo.dependsOn(bounds).

Execution order is in order of class definition, so SuperclassOfBoundingBox, then BoundingBox, and finally InfiniteBoundingBox.

The behavior I’d like guarantees all properties are instantiated because the end result is like doing this:

val box = InfiniteBoundingBox().apply { this.instance() }

When I say instantiated, I mean the object is created and has its final value after all overrides are applied. I don’t mean for the logic of which Var depends on which other Vars is applied at that point.

I was having trouble following so I made a minimal example. Let me know if I missed something:

abstract class A {
    abstract val myString: String
    
    init {
        println("Init (A)\tmyString = $myString")
    }
}

class B : A() {
    override val myString = "Hello World"
    //override val myString get() = "Hello World" // Try swapping this line with the one above.

    init {
        println("... constructor and static inits running in B ...")
    }
}

fun main() {
    val b = B()
    println("Main\t\tmyString = ${b.myString}")
}

EDIT: I’m not sure if it fits your design but have you considered using property delegates for your Var class? If you did, then you could have your types appear as normal types while still building a mechanism to access some kind of metadata such as dependencies between properties.

Ok, that makes sense, it definitely seems doable and I think it could be useful.

Sorry for overtaking the topic :slight_smile:

Interesting, I did end up finding a workaround for my original issue, but had I known this I might have been able to solve it differently. Using getter, you’re able to override the value in an interesting way.

But I’m not sure how this addresses the proposed instance{} block.

I have worked with property delegates before, never thought to use them for Var. Are you saying that instead of doing

val x: Var<Float> = Var { 0f }

I can just do

val x: Float = Var { 0f }

or something of that nature? If I can do this for Var<*> and have it work with all types automatically, I’d be interested in exploring it.

Glad the minimal example is helpful. It seems to me like the core issue relates to the order of static initializers.

True, my example doesn’t address the proposal. To be frank, I’m not sure the proposal is the right solution, or at least valuable enough to be added to the language, and overcome alternatives, and overcome the minus 100 point rule. I suspect that the problem has more discovery to be done.

The proposal has such a broad and wide-reaching impact that it would be hard to justify without a lot of usecases, studies and comparisons with other languages, etc.
We might be able to find an alternative to solve this one use-case though :slight_smile:


As for delegates, I can’t say it would perfectly fit your usecase. I’d imagine it would look like this:

val x: Float by variable { 0f }

The variable function could perform many actions with the property. It could register the property to some service, it could inspect assignment, it could be made lazy (similar to the stdlib lazy delegate), or more. However you can do all of this without property delegates since your Var class is essentually a delegate already just without the fancy Kotlin wrapping.

You can also access a properties delegate with reflection using getDelegate()
This might open up the option to do dependencies like this:

val y: Float by variable { x * 5 }.dependsOn(::x)

Of course, this is me guessing on one option for implementation. I imagine this would be implemented by getting the delegate and registering the dependency.

To be fair, using property delegates might hide some of the complexity while you’re implementing things. Technically, your Var class is a conceptual delegate class, and how it would be done in another language like Java. It might be a good idea to not use the Kotlin delegation mechanisms until you’ve implemented the core logic and then later add in some function that returns a Kotlin language property delegate.

Here’s a relevant StackOverflow thread: Kotlin Null Pointer Exception from value not being initialized prior to parent classes constructor/init block running - Stack Overflow

EDIT:
And here’s some links in the Kotlin docs:

This is a digression, but years ago I was experimenting with a similar concept of reactive variables. I just found my source code and although I was not able to launch it, you can find some examples below. Maybe you will get some ideas from it.

Example 1:

class User(firstName: String, lastName: String) {
    private val firstNameVar = Var.of(firstName)
    private val lastNameVar = Var.of(lastName)
    private val fullNameVar = firstNameVar + " " + lastNameVar

    var firstName by firstNameVar
    var lastName by lastNameVar
    var fullName by fullNameVar
}

fun main() {
    val user = User("John", "Smith")
    println(user.fullName) // John Smith
    user.firstName = "James"
    println(user.fullName) // James Smith
    
    // other way around
    user.fullName = "Rocky Balboa"
    println(user.firstName) // Rocky
}

Example 2:

val passwordVar = Var.of("")

var password by passwordVar
val msg by if_ (passwordVar.length ge 8) { "Password is ok" } else_ { "Password is too short" }

password = "pass"
println(msg) // Password is too short

password = "qwertyuiop"
println(msg) // Password is ok

Then I realized people prefer to work with reactive streams and with solutions like Compose. Anyway, it is an interesting thing to experiment :slight_smile:

If we’re digressing, I too made some modifications playing around with React (but the UI framework kind :wink: ) to learn delegates.

My focus was that I wanted to avoid writing the Props and State, which might look like this:

interface TickerProps : RProps {
    var startFrom: Int
}

interface TickerState : RState {
    var secondsElapsed: Int
}

and instead, use delegates to avoid creating those interfaces and having to use setState like this:

class Ticker : FComponent<FProps, FState>() {
    val startFrom by prop(0) // No need to define a specialized RProps class
    var secondsElapsed by state(startFrom) // No need to define a specialized RState class.
// ...
}

Never did anything with it but it was interesting to use for learning property delegates.
The code is messy and dead but you can see it here and the delegate implementation here.

1 Like