Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

this reference in a lazy initializer of kotlin extension property

I'm trying Kotlin and want to implement a lazy extension property for Activity:

/**
 * Activity module
 */
val Activity.activityModule: ActivityModule by lazy {
    ActivityModule(this)
}

The compiler errors with:

'this' is not defined in this context

How can I qualify this as Activity this? I have read a guide but can't get it. this@Activity says the reference is unresolved.

like image 896
Motorro Avatar asked Dec 21 '15 08:12

Motorro


2 Answers

Other answers here have pointed out that it is impossible to reference this within the stdlib's current implementation of the lazy receiver, and that one could implement their own delegate. So I decided to implement it and post it here...:

class LazyWithReceiver<This,Return>(val initializer:This.()->Return)
{
    private val values = WeakHashMap<This,Return>()

    @Suppress("UNCHECKED_CAST")
    operator fun getValue(thisRef:Any,property:KProperty<*>):Return = synchronized(values)
    {
        thisRef as This
        return values.getOrPut(thisRef) {thisRef.initializer()}
    }
}

Here is some code showing how to use it.

This implementation uses a weak hash map to store a separate value for each receiver...this comes with a couple of implications...:

  • distinct instances that are structurally equal will share the same value.

  • in some cases, values that have already been initialized for some receiver could be garbage collected which means that the initializer might be called again to re-initialize the value if it is accessed again.

like image 59
Eric Avatar answered Oct 05 '22 17:10

Eric


lazy calls initializer function when it is accessed first time and then stores the value returned by the initializer to return that value on successive accesses.

An instance of Lazy is capable of storing exactly one value. When you delegate extension property to a Lazy instance, you're getting a single instance of Lazy serving getValue requests from all instances of the receiver type, in your case it's Activity. This results in Lazy computing value only for first Activity and using that value on all subsequent calls for other instances of Activity.

Therefore while it's syntactically possible to pass an Activity to initializer as a receiver and refer it as this inside as @voddan suggests in this answer, the Lazy itself is not capable of storing different value for different receivers.

An ability to have an external storage for extension properties may likely be covered by "Attached properties" feature KT-7210. I don't think Lazy should have this ability as it complicates significantly its implementation.

like image 20
Ilya Avatar answered Oct 05 '22 18:10

Ilya