Home > Software engineering >  Check if a Kotlin variable is a function
Check if a Kotlin variable is a function

Time:11-12

I am currently learning Kotlin and am working through the By Example Guide. In the chapter Functional/Higher-Order Functions it explained how functions themselves can return functions with this example:

fun operation(): (Int) -> Int {                                    
    return ::square
}

fun square(x: Int) = x * x                                         

fun main() {
    val func = operation()                                          
    println(func(2))                                               
}

Since I had previously learned to check the type of a variable in a "when" block, I tried to do the same here. Checking if a variable is of type function.

fun operation(): (Int) -> Int {                                   
    return ::square
}

fun square(x: Int) = x * x                                       

fun main() {
    val func = operation()                                        
    when (func){
        is fun -> println(func(2))
        else -> println("Not a function")
    }                                              
}

But this throws an error "Type expected", I guess because fun in itself is not a type.

I tried searching for things like "kotlin check if variable is function" but all I could find where guides on how to check for primitives or classes, nothing even mentioned functions.

CodePudding user response:

Let's suppose you know nothing about func. (func is of type Any) You can easily check that it is a function by doing:

if (func is Function<*>) {
    ...
}

Or similarly use is Function<*> in a when branch.

However, this doesn't tell you the number or types of parameters, or the return type. Since you want to call the function with an Int here, it is important that you also check that the function has exactly one parameter of type Int. You can add a number after the word Function to check for a specific number of parameters,

if (func is Function1<*, *>) {
    ...
}

But that's where the simple stuff stops.

It is very hard to check for parameter types. You cannot just do this:

if (func is Function1<Int, Int>) {
    ...
}

Because generics are erased, the runtime is not able to distinguish between a Function1<Int, Int> and a Function1<Foo, Bar>, so you cannot check for a specific type parameter using is.

The only way I can think of is unfortunately, reflection.

// JVM only
if (func is Function1<*, *> && 
    (func as? KFunction<*> ?: func.reflect())?.parameters?.singleOrNull()?.type == typeOf<Int>()) {
    // this is an unchecked cast, which means the runtime won't check it
    // but it is fine, because the code above checked it
    println((func as Function1<Int, *>)(2))
}

operation can either return a KFunction, like your ::square, or a lambda. If it returns a lambda, the reflect experimental API (You'd need @OptIn(ExperimentalReflectionOnLambdas::class)) is used to turn it into a KFunction.

After we have a KFunction, we can inspect its single parameter (if it has one) and check if it is Int.

So checking for specific types of functions is quite a pain. I would recommend that you change your design to avoid doing this, if you ever find yourself needing to do this.

CodePudding user response:

You can do general check is Function<*> if you just need to find out given func object is a function or not. Also you can do restricted check for more specific function signatures, e.g. check it's a function with N arguments.

In the following code checks are arranged from more specific to less specific:

fun main() {
    val func = operation()
    when(func) {
        is Function2<*,*,*> -> println(func(4)) // function with 2 arguments
        is Function1<*,*>   -> println(func(3)) // function with 1 argument
        is Function<*>      -> println(func(2)) // function in general
        else -> println("Not a function")
    }
}

And the output of this code is 9 because func is both a "Function" & a "Function with 1 argument", but "Function with 1 argument" condition is matched earlier.

P.S. Function22 (f with 22 arguments) is largest built-in into Kotlin

  • Related