How to save a function reference as the value in a Map type, and invoke it with a parameter later on in 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.

$functionname() or call_user_func($functionname)

in kotlin, there's no way to check the generic parameters at runtime in general case (like just checking the items of a list<t>, which is only a special case), so casting a generic type to another with different generic parameters will raise a warning unless the cast lies within variance bounds.

there are different solutions, however:

  • you have checked the type and you are quite sure that the cast is safe. given that, you can suppress the warning with @suppress("unchecked_cast").

    @suppress("unchecked_cast")
    val waypointlist = list as? list<waypoint> ?: return null
    
  • use .filterisinstance<t>() function, which checks the item types and returns a list with the items of the passed type:

    val waypointlist: list<waypoint> = list.filterisinstance<waypoint>()
    
    if (waypointlist.size != list.size)
        return null
    

    or the same in one statement:

    val waypointlist = list.filterisinstance<waypoint>()
        .apply { if (size != list.size) return null }
    

    this will create a new list of the desired type (thus avoiding unchecked cast inside), introducing a little overhead, but in the same time it saves you from iterating through the list and checking the types (in list.foreach { ... } line), so it won't be noticeable.

  • write a utility function that checks the type and returns the same list if the type is correct, thus encapsulating the cast (still unchecked from the compiler's point of view) inside it:

    @suppress("unchecked_cast")
    inline fun <reified t : any> list<*>.checkitemsare() =
            if (all { it is t })
                this as list<t>
            else null
    

    with the usage:

    val waypointlist = list.checkitemsare<waypoint>() ?: return null
    

you can use np.full_like:

b = np.full_like(a, 1)

this will create an array with the same properties as a and will fill it with 1.

in case you want to fill it with 1 there is a also a convenience function: np.ones_like

b = np.ones_like(a)

your example does not work because b.fill does not return anything. it works "in-place". so you fill your b but you immediatly overwrite your variable b with the none return of fill. it would work if you use it like this:

a=np.array([[2,2], [2,2]])
b=np.copy(a)
b.fill(1)

turns out that the kotlin version 1.3.11 is causing the trouble. i have downgraded it to 1.3.10 and it works perfectly fine. ktor will receive a fix in the next minor release.

source - kotlin slack, multiplatform channel.


Tags: Kotlin