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

杀马特。学长 韩版系。学妹 提交于 2019-11-27 23:49:51

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

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?

I'm not sure I completely understand your use case but this may be what you're looking for:

fun initHolder(x: Int = 0, holderAction: Holder.() -> Unit) : Holder {
    var h: Holder? = null
    h = Holder(x) { h!!.holderAction() }
    return h
}

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

This works because holderAction is a lambda with a receiver (Holder.() -> Unit) giving the lambda access to the receiver's members.

This is a general solution since you may not be able to change the signature of the respective Holder constructor. It may be worth noting this solution does not require the class to be open, otherwise a similar approach could be done with a subclass using a secondary constructor.

I prefer this solution to creating a SelfReference class when there are only a few number of classes that need the change.

You may want to check for null instead of using !! in order to throw a helpful error. If Holder calls action in it's constructor or init block, you'll get a null pointer exception.

I'm pretty sure you can achieve the same results in a more readable and clear way using something like this:

fun <T> selfReferenced(initializer: () -> T) = initializer.invoke()
operator fun<T> T.getValue(any: Any?, property: KProperty<*>) = this

and later use

val valueName: ValueType by selfReferenced{
    //here you can create and use the valueName object
}

Using as example your quoted question https://stackoverflow.com/a/35050722/2196460 you can do this:

val textToSpeech:TextToSpeech by selfReferenced {
TextToSpeech(
        App.instance,
        TextToSpeech.OnInitListener { status ->
            if (status == TextToSpeech.SUCCESS) {
                textToSpeech.setLanguage(Locale.UK)
            }
        })
    }

Inside the selfReferenced block you can use the outer object with no restrictions. The only thing you should take care of, is declaring the type explicitly to avoid recursive type checking issues.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!