Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing a Kotlin util function which provides self-reference in initializer

I'm trying to generalize my hack from an answer to another question.

It should provide a way to reference a value which is not constructed yet inside its initializer (of course, not directly, but in lambdas and object expressions).

What I have at the moment:

class SelfReference<T>(val initializer: SelfReference<T>.() -> T) {
    val self: T by lazy {
        inner ?: throw IllegalStateException("Do not use `self` until initialized.")
    }

    private val inner = initializer()
}

fun <T> selfReference(initializer: SelfReference<T>.() -> T): T {
    return SelfReference(initializer).self
}

It works, see this example:

class Holder(var x: Int = 0,
             val action: () -> Unit)

val h: Holder = selfReference { Holder(0) { self.x++ } }
h.action()
h.action()
println(h.x) //2

But at this point the way in which initializer references the constructed value is self property.

And my question is: is there a way to rewrite SelfReference so that initializer is passed an argument (or a receiver) instead of using self property? This question can be reformulated to: is there a way to pass a lazily evaluated receiver/argument to a function or achieve this semantics some way?

What are the other ways to improve the code?


UPD: One possible way is to pass a function that returns self, thus it would be used as it() inside the initializer. Still looking for other ones.
like image 981
hotkey Avatar asked Jan 30 '16 10:01

hotkey


1 Answers

The best I have managed to produce while still being completely generic is this:

class SelfReference<T>(val initializer: SelfReference<T>.() -> T)  {
    val self: T by lazy {
        inner ?: throw IllegalStateException("Do not use `self` until initialized.")
    }

    private val inner = initializer()
    operator fun invoke(): T = self
}

Adding the invoke operator lets you use it in the following way:

val h: Holder = selfReference { Holder(0) { this().x++ } }

This is the closest I got to make it look like something you would "normally" write.

Sadly I think it is not possible to get completely rid of a explicit access to the element. Since to do that you would need a lambda parameter of type T.() -> T but then you wouldn't be able to call that parameter without an instance of Tand being T a generic there is no clean and safe way to acquire this instance.

But maybe I'm wrong and this helps you think of a solution to the problem

like image 100
Eric Martori Avatar answered Nov 01 '22 14:11

Eric Martori