3

Let's say I have class:

class Foo {
   fun doSomething(param1: Int, param2: String, param3: String) 
}

and a data class

data class Params(
   val param1: Int,
   val param2: String,
   val param3: String)

Now I want to use the data class arguments to send them to the function, is there a way to do that? Lets say something similar to:

val myParams = Params(1, "2", "3")
val foo = Foo()
foo.doSomething(myparams)

Or by some sort of transformation or method naming. as:

 execute(foo, foo::doSomething, myParams)
htafoya
  • 18,261
  • 11
  • 80
  • 104
  • 1
    I don't see any other way than reflection or code generation. So generally speaking: no, this is not possible in Kotlin without some tricks. The best you can do is to overload `doSomething()` to accept `Params` or create extension function. – broot Oct 11 '21 at 22:18
  • Yes I don't want to overload as it will defeat the purpose I'm looking. How would it be with reflection? – htafoya Oct 11 '21 at 22:31

1 Answers1

3

I doubt this is possible in Kotlin without some tricks. Possible solutions are reflection API and code generation.

Example using reflection:

fun main() {
    val myParams = Params(1, "2", "3")
    val foo = Foo()

    invokeWithParams(foo::doSomething, myParams)
}

fun <T : Any, R> invokeWithParams(func: KFunction<R>, params: T): R {
    val paramValues = func.parameters.map { kparam ->
        (params::class as KClass<T>)
            .memberProperties
            .single { it.name == kparam.name }
            .get(params)
    }.toTypedArray()
    return func.call(*paramValues)
}

It should work for static functions, member functions, and extension functions. It may fail with some rarer cases. You should probably add some error handling, e.g. checks if params match.

It won't work on anything else than JVM as reflection is still very limited on other targets.

Also, I'm not entirely sure about this unsafe cast. I think it can't fail, but I'm not 100% sure about it.

Update:

We can make it a little more funny by converting the function to extension operator invoke:

operator fun <T : Any, R> KFunction<R>.invoke(params: T): R

Then we can use it with any function like this:

(foo::doSomething)(myParams)

I'm not sure if this is a good idea though as it is more confusing than an explicit call to the utility function.

Lior Bar-On
  • 10,784
  • 5
  • 34
  • 46
broot
  • 21,588
  • 3
  • 30
  • 35
  • Nice, I tried something similar but I was having problems with the generics, I was missing `as KClass` – htafoya Oct 12 '21 at 15:53
  • Yes, in Kotlin `dog::class` returns `KClass` meaning that we can't really use it to access any members of `dog`. I believe the reason is that otherwise it would be possible to: `(dog as Animal)::class` and then use it to access `Dog` members with `Animal` object. But if we are sure that we use exactly the same `dog` object both to acquire `KClass` and to use its members, I think it is safe to cast `KClass` to `KClass`. – broot Oct 12 '21 at 17:35