Shanki Bansal
Shanki Bansal

Reputation: 1740

Kotlin: store any type of function reference in a variable

Is it possible to have a variable that can holds any type function.

Like :

    fun method1(par: Boolean){}
    fun method2(par: Boolean) : Int{return 1}
    fun method3(par: Boolean, par2: Boolean) : Int{return 1}

    var funtionHolder : ((Any)->Any) ?= null  //What should I write here?? so to hold any type of function

   fun method4(){
        .........
        funtionHolder = ::method1  //getting compile time error
        .........
        funtionHolder = ::method2  //getting compile time error
        .........
        funtionHolder = ::method3  //getting compile time error
    }

After holding the function_reference I need to invoke it later. So I need to holds it parameter type and state also.

Upvotes: 1

Views: 1728

Answers (3)

Jayson Minard
Jayson Minard

Reputation: 85946

You can hold them in a KFunction<Any> or its superclass KCallable<Any> because you know nothing about the parameter list and nothing about the return type, so you have to go to something that can reference at that level of abstraction. These instances can then be invoked more generically using the call() or callBy() methods. (this requires the kotlin-reflect dependency). To do something safer and to call like a normal function you'd have to cast back to the specific function type later.

If you want to avoid this, you'll need to unify your signatures to something you can point to with another function type (i.e. KFunction1 or KFunction2). Otherwise how you'll call this, what you'll do with it will be up to you at this point because you erased all the information that allows you to easily call the function.

val functionHolder1: KFunction<Any> = ::method1 // success!
val functionHolder2: KFunction<Any> = ::method2 // success!
val functionHolder3: KFunction<Any> = ::method3 // success!

You can then make a DeferredFunction class to hold these along with parameters you want to later pass, and then invoke it whenever in the future.

class DeferredFunction(val function: KFunction<Any>, vararg val params: Any?) {
    @Suppress("UNCHECKED_CAST")
    operator fun <T> invoke(): T {
        return function.call(params) as T
    }
}

fun whatever(name: String, age: Int): String {
    return "$name of age $age"
}

val functionHolder = DeferredFunction(::whatever, "Fred", 65)

println(functionHolder<String>()) // "Fred of age 65"

You do not need the generic return type on the invoke function and could just make it return Any or call it as functionHolder<Any>() but it is nice if you know what to expect for the return. You can decide what to do there based on your actual use case. Also no need to special case for no parameters, just don't pass any, i.e. DeferredFunction(::otherFunc)

Upvotes: 3

Shanki Bansal
Shanki Bansal

Reputation: 1740

With reference from Jayson's answer, added extra code to hold the state of the function by using vararg and spread operator(*).

    var functionHolder: KFunction<Any> ?= null
    var paramsHolder : Array<out Any?> ?= null

    fun hold(functionReference : KFunction<Any>, vararg args : Any?) {
        this.functionHolder = functionReference
        this.paramsHolder = args
    }

    fun release() {
        if (functionHolder != null) {
            if (paramsHolder != null) {
                functionHolder?.call(*paramsHolder!!)
            } else {
                functionHolder?.call()
            }
        }
    }

......

    fun method3(par: Boolean, par2: Boolean) : Int{return 1}

......

    hold(::method3, true, false)
    release()//it works

Upvotes: 0

AIMIN PAN
AIMIN PAN

Reputation: 1675

No. Kotlin is static typed language and doesn't allow this. Else what happens when this is called?

functionHolder->invoke(3)

and when functionHolder is assigned a lamda that doesn't take parameter?

Upvotes: -1

Related Questions