`Lazy` doesn't inherit `ReadOnlyProperty`

Kotlin doesn’t force us to use ReadOnlyProperty and ReadWriteProperty when we want to use delegation. And it’s great because it gives us the possibility to use extension functions. But why not declare these interfaces in all places where it’s possible?

Example (Lazy interface isn’t inhereted from ReadOnlyProperty):

I’m implementing factory pattern using delegation. Because It gives me more flexibility:

interface Factory {
    val view: ReadOnlyProperty<Any?, View>
    // ...
}

And I want to implement view property using lazy. So I write:

object FactoryImpl : Factory {
    override val view: ReadOnlyProperty<Any?, View> = lazy { ViewImpl() } // won't compile
}

But I can’t do this because lazy function returns Lazy interface which isn’t inherited from ReadOnlyProperty. Delegation feature for Lazy implemented via extension function:

public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

And I believe that I understand why Kotlin team did this via extension function not via member function. Because they consider Lazy as separate service. It’s not connected with delegation. Delegation is additional possibility which shouldn’t be mentioned in Lazy interface. I understand their decision but I still believe that code above should compile.

As possible solution for Kotlin team I offer to create new interface called something like LazyProperty:

interface LazyProperty<in R, out T> : Lazy<T>, ReadOnlyProperty<R, T>

And lazy function should return this LazyProperty.

Conclusion

In my opinion this problem and problem that ReadWriteProperty is not inherited from ReadOnlyProperty makes working with delegation objects difficult especially for library makers who may want to operate with ReadOnlyProperty and ReadWriteProperty objects.

2 Likes

The motivation behind having these interfaces in the stdlib is to provide a way to return anonymous objects which one can delegate properties to, e.g.:

fun <T> initOnce(): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
...
}

Without these interfaces one had to introduce a new type that has getValue/setValue as members or extensions.

Lazy interface doesn’t implement ReadOnlyProperty in order not to burden the interface with an unrelated concept.

If you want to use instances of Lazy where ReadOnlyProperty is required, you could easily create a wrapper:

fun <T> Lazy<T>.asProperty() = object : ReadOnlyProperty<Any?, T> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): T = 
        this@asProperty.getValue(thisRef, property)
}

And then use it like

object FactoryImpl : Factory {
    override val view: ReadOnlyProperty<Any?, View> = lazy { ViewImpl() }.asProperty()
}

Why would you want to operate with these interfaces in your library instead of creating your own abstractions?