Idea on improving work with '?.'

Hello, I was wondering about one language feature that could improve readability. Imagine the following contrived example:

data class Person(
    val name: String, 
    val address: Address)

data class Address (
    val house: Int, 
    val streetName: String)

fun test(persons: List<Person>) {
    for(person in persons) {
        val matched = person.address.streetName.startsWith("a")
        if (matched) println(person)
    }
}

The test function gets a list of persons and prints the ones that have their address starting with letter “a”. Now, imagine, that we change the Person class in such a way, that the address is a nullable type:

data class Person(
    val name: String, 
    val address: Address?)

to fix the test function, we simply skip the persons having undefined address like so:

fun test(persons: List<Person>) {
    for(person in persons) {
        val matched = person.address?.streetName?.startsWith("a") ?: continue
        if (matched) println(person)
    }
}

This works, but my problem is that the language forces me to write ? after the streetName, eventhough I know that the street name is always defined. It would be nice if I could write this instead:

        val matched = person.address?.streetName.startsWith("a") ?: continue

Basically, with address == null we have a shortcut towards continue and we don’t have to check if streetName is null. This would allow avoiding the annoying situation that you have to change from:

a?.b.c.d.e.f ?: continue

to

a?.b?.c?.d?.e?.f ?: continue

What do you think?

I already reported this issue in this ticket: KT-19173. You probably saw how they done it in C# and they require you to write a safe-call operator only where the actual null may be returned. Kotlin on the other hand requires you to use them on every subsequent safe-call after the first usage. It was a design choice, but I also disagree with this one.

1 Like

This is a breaking change.
Please consider that person?.address?.toString() isn’t null.

1 Like

The point is of how they should interpret chain calls. Instead of:

val matched = (person.address?.streetName.startsWith("a")) ?: continue

they interpret it as:

val matched = ((person.address)?.streetName)?.startsWith("a") ?: continue

Ok, I got it.

The ?. operator defines an optional invocation, instead the . imposes an invocation.

In the chain a.b()?.c().d(), the c method may be inoked, instead the a and d method must be invoked.

Your example a?.b()?.c() may result in a waring, because a?.b() is never null (for example).

This improvement can make the code harder to undestand, this is what I think.

Thanks for the answers so far! When I look at the example from the clean code perspective, it is better when there are not as many questions in my code, because they do essentially represent individual ‘if’ statements (for each of which I should, perhaps, provide the test coverage?).

I can rewrite the solution in a less elegant but much cleaner manner as follows:

if (person.address == null) continue
val matched = person.address.streetName.startsWith("a")

So we only need one ‘if’ statement here. Therefore, from the language design perspective I would like to know: why write those extra questions in the first place? Does it have some benefits? Does it make parsing harder/slower? Perhaps some Kotlin expert knows this?

1 Like

Write clean code and keep the code near your thought.

The used style should be a consequence of how you think an algorithm.

3 Likes

your are not answering question :neutral_face: