Is there a way to write a "type narrowing" function?

I’m writing some code that involves iterating over heterogeneous collections, and I’m wondering if there’s a way to write a function that encodes an internal “is” check. I’m used to TypeScript, which has a lot more of this kind of thing.

For example,

data class Foo(val color: String)

// This syntax borrowed from TypeScript
// This works if I return Boolean, but doesn't help type narrowing in other contexts
fun isRedFoo(foo: Any?): foo is Foo {
    return foo is Foo && foo.color == "red"
}

fun doSomethingToFoo(foo: Foo) { ... }

val items = listOf<Any>(...)
items.forEach {
    when {
        // If I use Boolean for the return type of isRedFoo, Kotlin yells at me because
        // it doesn't know that "it" is of type Foo
        isRedFoo(it) -> doSomethingToFoo(it)
        else -> println("skipping $it")
    }
}

I know I can write (it is Foo && isRedFoo(it)) -> ... but that’s deeply less satisfying (and that’s the only criterion that really matters :wink:). It also doesn’t work if the type that doSomethingToFoo expects changes (I’d kind of like to decouple that from this part of the code).

what about
items.filterInstanceOf< Foo >()
.filter { it.color==“red” }
.forEach { item -> }

1 Like

There is an experimental feature called Contracts, which does exactly that. Look it up yourself, and then ask if you still have trouble.

4 Likes

Using contracts:

data class Foo(val color: String)

@ExperimentalContracts
fun isRedFoo(foo: Any?): Boolean {
    contract {
        returns(true) implies (foo is Foo)
    }

    return foo is Foo && foo.color == "red"
}

fun doSomethingToFoo(foo: Foo) { }

@ExperimentalContracts
fun test() {
    listOf<Any>().forEach {
        when {
            isRedFoo(it) -> doSomethingToFoo(it)
            else -> println("skipping $it")
        }
    }
}

Did this from just playing around with contracts. I’m not sure if it’s completely correct, but it works.

5 Likes

I think you mean:
returns(true) implies (foo is Foo)

3 Likes