How to save a function reference as the value in a Map type, and invoke it with a parameter later on in Kotlin? - kotlin

val specials:Map<String, (Any)->Unit> = mapOf(
"callMe1" to {asParam1()},
"callMe2" to {asParam2()}
)
fun asParam1(num:Int) {
println(num)
}
fun asParam2(text:String) {
println(text)
}
fun caller() {
specials["callMe1"]?.invoke("print me")
specials["callMe2"]?.invoke(123)
}
fun main(args: Array<String>) {
caller()
}
My requirement is simple, I want to save the function asParam1 and asParam2 as a value in the variable specials. And invoke it later on by fetching the value from a Map.
However, the compiler doesn't like it:
Error:(1, 40) Type inference failed. Expected type mismatch: inferred
type is Map Unit> but Map Unit> was
expected
Error:(1, 69) No value passed for parameter num
Error:(1, 96) No value passed for parameter text
While this task is pretty simple in a weak typed language, I don't know how to do in Kotlin. Any help would be welcome. Thanks!

The correct syntax is "calllme" to ::asParam1.
But then the signatures will be wrong because the Map expects type (Any)->Unit and yours have (Int)->Unit and (String)->Unit. Here is an example that does not produce the error:
val specials:Map<String, (Any)->Unit> = mapOf(
"callMe1" to ::asParam1,
"callMe2" to ::asParam2
)
fun asParam1(num:Any) {
if(num is Int) println(num)
}
fun asParam2(text:Any) {
if(text is String) println(text)
}
fun caller() {
specials["callMe2"]?.invoke("print me")
specials["callMe1"]?.invoke(123)
}
Keep in mind, your code for the caller has special knowledge about how to call each of your functions (i.e., the correct parameter types), but the compiler does not have this same knowledge. You could accidentally call asParam1 passing a String instead of an Int (which is what your caller function was doing, I fixed it in my example) and that is not allowed. Which is why I changed the signatures of both asParam* to accept Any parameter, and then validated the expected type in each function (ignoring bad types).
If your intent is to pass integers in addition to strings to asParam2(), then change the body to test for both Int and String and convert the integer to a string.

When you write { asParam1() }, you create a lambda with an executable code block inside it, so you need to properly call the function asParam1(...), which requires an Int argument.
So, the first change you need to make is: { i -> asParam1(i) }.
But this code will still not pass the type checking, because, matching the type of the map, the lambda will be typed as (Any) -> Unit (the values in the map should all be able to accept Any, and a function that expects a narrower type cannot be a value in this map).
You then need to convert the Any argument to Int to be able to invoke the function: { i -> asParam1(i as Int) }
Finally, the map will look like this:
val specials: Map<String, (Any) -> Unit> = mapOf(
"callMe1" to { i -> asParam1(i as Int) },
"callMe2" to { s -> asParam2(s as String) }
)
The invocation stays unchanged, as in your code sample.
The function reference syntax (::asParam1) would allow you to reference a function that already accepts Any, it would not implicitly make the conversion described above. To use it, you would have to modify your functions to accept Any, as in #Les's answer.

Related

Check if a Kotlin variable is a function

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.
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.
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

Kotlin: generic cast function parameter

Taking my first steps in Kotlin, I'm struggling to find the correct signature for a function that receives an instance of a known class along with the desired output class and then looks in a map of converter lambdas whether the conversion can be done.
Here's an example for Long:
private fun <T> castLong(value: Long, clazz: Class<out T>): T {
// map lookup removed for simplicity
return when (clazz) {
String::class.java -> { value.toString() }
else -> { throw IllegalArgumentException("Unsupported Cast") }
}
}
Where T is the class of the desired return value - let's say String. One should be able to call castLong(aLongValue, String::class.java) and receive an instance of String.
But the compiler says:
Type mismatch: inferred type is String but T was expected
This seems like it should be possible as it is quite straightforward so far but even playing around with reified and other constructs didn't yield any better results.
It happens because it can't smart cast String to T, you have to manually cast it.
Furthermore, since you said you are taking your first steps in Kotlin, I leave here two other "advices" not strictly related to your question:
you can get the class of T making it reified
the brackets of a case using when aren't necessary if the case is one line
private inline fun <reified T> castLong(value: Long): T {
// map lookup removed for simplicity
return when (T::class.java) {
String::class.java -> value.toString()
else -> throw IllegalArgumentException("Unsupported Cast")
} as T
}

Using implicit variable in function literal

I started reading Kotlin course book. I stopped on function literals. Here I have a code:
val printMessage = { message: String -> println(message) }
printMessage("hello")
printMessage("world")
Then I have an information that I can omit parameter type:
{ message -> println(message) }
And now I have next step:
"In fact, Kotlin has a neater trick. If there is only a single parameter and the type can beinferred, then the compiler will allow us to omit the parameter completely. In this case, itmakes the implicit variable it available:
{println(it)}
And now after using this code I get an error "unresolved reference: it" and "too many arguments for public operator fun invoke(): ??? defined in kotlin.Function()":
val printMessage = {println(it)}
printMessage("print something")
My question is how to use implicit variable in single paramenter function literal?
See the Kotlin documentation, specifically where it says:
If the compiler can figure the signature out itself, it is allowed not
to declare the only parameter and to omit ->. The parameter will be
implicitly declared under the name it.
In your case, the compiler (at least up to current version 1.3.31) can't figure the signature out itself:
val printMessage = {println(it)}
But if you give your printMessage variable an explicit type, it will work:
val printMessage: (String) -> Unit = { println(it) }
You always need to provide all information about all generic parameters. If you want to omit it, it needs to be inferable from some other part of the code. The only information you provide though is that you want printMessage to be a lambda. So it assumes it to be of type ()->Unit. This is because you don't declare a parameter for the lambda itself. The implicit parameter it is therefore not usable.
val printMessage = { it: String -> println(it) }
val printMessage: (String)->Unit = { println(it) }
Simply put: If you're inside a lambda with one parameter, the implicit it can be used as this parameters name, but a reference named it within the body of the lambda doesn't declare the single parameter.

How this method returns different type than declared?

I found the following code in a project and it is used to convert throwables to custom exceptions.
I am confused how it is working.
The expected return type is: Single<out Response<T>>
But the method body returns something different: eg Single<Exception>
Is there explanation? it is compiling and working fine!
class ThrowableObservableFunc1<T> : Function1<Throwable, Single<out Response<T>>> {
override fun invoke(throwable: Throwable): Single<out Response<T>> {
val clientConnectionException = Exception(throwable.message)
return when (throwable) {
is ConnectException, is UnknownHostException, is SSLException ->
Single.error(clientConnectionException)
is SocketTimeoutException ->
Single.error(TimeoutConnectionException(throwable.message))
else -> Single.error(UnexpectedException(throwable.message))
}
}
}
Single.error doesn't return a Single<Exception>, it returns a Single<T> according to its signature:
public static <T> Single<T> error(final Throwable exception)
Since the T type parameter isn't used anywhere in the signature other than in the return type (that is to say, it doesn't take a T as a parameter, for example), it can be inferred to whatever type you need returned from this function wrapped into a Single.
For some more examples:
val stringError: Single<String> = Single.error(Exception()) // T inferred to be String
val intError: Single<Int> = Single.error(Exception()) // T inferred to be Int
Or, if you want to specify the parameter when you're calling the function directly (in which case you could omit the types for the variables on the left hand side):
val stringError: Single<String> = Single.error<String>(Exception()) // T specified as String
val intError: Single<Int> = Single.error<Int>(Exception()) // T specified as Int
These all make sense because a Single of any type can error the exact same way, regardless of what type a non-errored value it produces would have.

Kotlin - Pass array of functions as argument to function

I have this function that (theoretically) accept an array of functions as argument.
fun execute(afterDay: Long, listeners: Array<(List<String>) -> Unit>)
In the client class I trying to call this execute method and pass functions as parameter:
fun updateMovies(ids: Array<String>){
}
fun getNewIds() {
GetImdbIds(kodein).execute(daysBack.toEpochDay(), [::updateMovies])
}
But it doesn't compiles.
What I'm doing wrong?
the error:
Error:(29, 59) Kotlin: Type inference failed. Expected type mismatch: inferred type is Array<KFunction1<#ParameterName Array<String>, Unit>> but Array<(List<String>) -> Unit> was expected
Error:(29, 59) Kotlin: Unsupported [Collection literals outside of annotations]
I got this to work by making two changes.
First, your updateMovies function as written takes an Array<String>, when your listeners wants functions that take List<String>. So, we can make this change:
fun updateMovies(ids: List<String>) {
TODO()
}
Next, if you create your array of function references using arrayOf() instead of attempting an illegal array literal, this should compile:
GetImdbIds(kodein).execute(1L, arrayOf(::updateMovies))