Dol.Gopil
Dol.Gopil

Reputation: 125

How to check if an argument is a lambda

I am processing a collection of instances of MyCustomType as follows:

fun runAll(vararg commands: MyCustomType){
    commands.forEach { it.myMethod() }
}

Besides instances of MyCustomType, I'd like to pass and process lambdas of type () -> Unit, something like this:

fun runAll(vararg commands: Any){
    for(command in commands){
        if (command is MyCustomType){
            command.myMethod()
        } else
        if(command is () -> Unit){
            command.invoke()
        }
    }
}

The line if(command is () -> Unit){ does not compile with the following message: Cannot check for instance of erased type: () -> Unit.

Is there a way to check if an object is () -> Unit at runtime?

I have seen another answer that recommends using wildcards. I do not think that is relevant: my code does not use generics.

Upvotes: 2

Views: 1630

Answers (2)

Roland
Roland

Reputation: 23252

Is there a reason why your MyCustomType isn't a Function0 by itself? If it would be, you could just use a vararg commands : () -> Unit and you could then just call command() regardless on what is passed (which is by the way the same as calling command.invoke()). You can do that even now with your current code:

Change signature for runAll to:

fun runAll(vararg commands: () -> Unit) { ...

Then call it using:

runAll(MyCustomType()::myMethod, { println("self-built-function") } /*, ... */)

or the same written a bit differently with its own val:

val customObj = MyCustomType()
runAll({ customObj.myMethod() }, { println("self-built-function") })

Moreover, if you require all commands to have a certain return value you can do so still, e.g. just use vararg commands: () -> String if all your commands must return a String.

Using Any in that regard may lead to headaches in the future. If you narrow down your type, you are at least sure that you will always have commands behaving in the same way. Moreover you get the best code completion support that way. With Any you can pass anything, but it doesn't run everything.

If you only want to check whether a parameter is a lambda, you can also use the following:

someObj is Function<*>

However you can't call invoke then without casting it to a specific function type, i.e.: Function0, Function1, etc. Regarding Function0, etc. Todd already answered that. But as he also mentioned in his warning: there is a downside to that. I can only recommend omitting Any whenever you can.

Upvotes: 0

Todd
Todd

Reputation: 31690

Kotlin is going to compile your lambda into an instance of Function0. If you know this, you can use a when block to compare and smart cast quite nicely:

fun runAll(vararg commands: Any) {
    commands.forEach {
        when(it) {
            is Function0<*> -> it.invoke()
            is MyCustomType -> it.myMethod()
        }
    }
}

And then to call it:

fun main(args: Array<String>) {
    runAll(
        MyCustomType(), 
        { println("Lambda!") }
    )
}

Warning: The downside of this approach is that with type erasure, you don't know if you're getting a Function0<Unit> or a Function0<Int>, because the type isn't available at runtime for you to make that determination. That means somebody could give you a lambda that returns something and you would ignore the results.

Upvotes: 2

Related Questions