Passing lambdas


#1

I am currently learning about lambdas and have the following function…

fun findNumbers(numbers: List, predicate: (Any) -> (Boolean)) {
}

I want to pass in a list array containing different data types and return a boolean based on a check like this…

val data = listOf(1, 2, “Hello”, “Kotlin”, 3, 1.23, 2L, 3.21f, 4)
val result = findNumbers(data, { it is Int })

I want the result to be a filtered list array of Int which uses my check it is Int as the filter which i will iterate over and print
I know I can do this easier with data.filter { it is Int } but I am trying to figure it out with a lambda passed to the function


#2

If you want the function to return a List<Int>, then this should work:

fun findNumbers(numbers: List<Any>, predicate: (Any) -> (Boolean)): List<Int> =
    numbers.filter { predicate(it) }.map { it as Int }    

fun main(args: Array<String>) {
    val data = listOf(1, 2, "Hello", "Kotlin", 3, 1.23, 2L, 3.21f, 4)
    val result = findNumbers(data) { it is Int }
    println(result)  //  [1, 2, 3, 4]
}

#3

But isn’t the point that the lambda I am passing is executed and I shouldn’t have to define the ‘it is Int’ part again in the function body?


#4

The problem is that the compiler doesn’t know that the items emerging from the filter will be Ints. All it knows is that they will be of type Any. Consequently, if they’re not Ints, the cast (as Int) will fail at runtime.

A much better way of doing this would be to return a List<Any> rather than a List<Int>. You can then pass in any predicate without worrying about what type the results will be and the List should still print fine:

fun findNumbers(numbers: List<Any>, predicate: (Any) -> (Boolean)): List<Any> =
    numbers.filter { predicate(it) }    

fun main(args: Array<String>) {
    val data = listOf(1, 2, "Hello", "Kotlin", 3, 1.23, 2L, 3.21f, 4)
    val result = findNumbers(data) { it is Int }
    println(result)  //  [1, 2, 3, 4]
    val result2 = findNumbers(data) { it is String }
    println(result2)  //  [Hello, Kotlin]
}

#5

By the way, if your goal is to filter and cast a list in the same step, kotlin has a function in the standard lib for this (in package kotlin.collections). It uses generics to figure out the output type so you don’t need to specify it from a block.

/**
 * Returns a list containing all elements that are instances of specified type parameter R.
 */
public inline fun <reified R> Iterable<*>.filterIsInstance(): List<@kotlin.internal.NoInfer R>

Example:

val data = listOf(1, 2, "Hello", "Kotlin", 3, 1.23, 2L, 3.21f, 4)
val result = data.filterIsInstance<Int>()
println(result)  //  [1, 2, 3, 4]
val result2 = data.filterIsInstance<String>()
println(result2)  //  [Hello, Kotlin]