Is operator ? needed

Null-safety called up for escape unexpected NPE. But sometimes it is logically required. Then were created nullable and not-null types. Not-Null fields will spot NPE while compilation. And Nullable fields must be handled by programmer in case of null. Also was created operator “?”, which indicates can be field null or not. This operator also must be applied to nullable fields, when their inner fields or methods were invoked. Respectively, return type will become nullable or not as well, as their parent field. But is this needed?
If we have been invoked field or method of nullable field, return type can automatically sets itself to nullable. It easy to see, can be return type nullable or not. And handle it accordingly. And then operator “!!”, which looks so bad, will lost their need.
Because constructions like this val l = b?.length ?: -1 and this val l = b!!.length looks difficult and overloaded.
May be, I don’t understand all factors and if it is true, please, tell to me. Thank you!

What about it? There are nothing answers here. Therefore, can it be a new improvement of Kotlin design? I beileve! Please, give me a feedback.

I’m afraid your English is not easy to follow, which may not be helping your argument.

I think you’re suggesting that all calls should be null-safe, i.e. that the function-call operator . should always act like the safe-call operator .? whenever used on a nullable operand — is that right?

If so, I don’t think that’s a good idea.⠀Yes, it would prevent some compilation errors.⠀But those errors can be useful.⠀Ignoring the call and evaluating to null isn’t always the right thing to do, and a compiler error can be a helpful reminder to perform some other action.⠀It can also indicate design errors and other bugs where a value shouldn’t have been nullable.

Experience with languages such as Java shows that you need to consider nulls; you need to know when a value could be null, and how to handle it if so.⠀Kotlin makes that as easy as possible: its nullable types and type inference track nullability for you, and its safe-call and elvis operators give easy ways to handle it.⠀But this change would simply sweep all that under the carpet, hiding problems from view without really addressing them.⠀It would turn some compile-time errors (which are quickly seen and fixed) into bugs (which cause unwanted behaviour at run-time, and need careful manual testing to find).

You also complain that the !! not-null assertion is ugly.⠀Yes, it is — that was a deliberate choice by the designers of the language, because it’s an operator you should rarely, if ever, need to use.⠀It’s a code smell; it usually indicates either a coding error (failing to handle a null that needs to be handled) or a design error (e.g. wrongly using a nullable type).⠀So having it look ugly reflects its usage.

3 Likes

Sorry, I don’t know English as well as wants. I will strive to speak better)

Almost the same. I suggested to make return type of all methods, that called from nullable field, also nullable. For example:

val a: String? = "some value or null"
a.someMethod() //Return type automatically sets to nullable, because of a is nullable.

In another case, when field is non-null return type will not be changed. Example:

val a: String = "some value, not null"
a.someMethod() //Return type is non-null

Ok, I agree with you.

I starts this topic after code, like this:

properties?.get(...)?.set(...)?.close()

And this:

properties!![...]!![...]

Thank you, for you answer!

How whould you go about String?.orEmpty()

Main purpose of forcing ? is to make explicit the fact that an operation may or may not be launched according to its receiver nullability. Let’s take this example:

val a: String? = ...
a.someMethod()

It is not very explicit that someMethod() will be launched only if a is not null. However, with ? it becomes clear that developer intent is to run someMethod if a is not null, and ignore it otherwise.

I think that Kotlin main purpose is to make code more explicit, not necessarily shorter, in order to help developers ask themselves the right question about their code as they’re writing it.

However, what you ask can be partially achieved using extension methods :

fun String.someMethod() {
    println(this)
}

fun String?.someMethod() = this?.let { it.someMethod() }

fun main() {

    var a: String? = null
    
    a.someMethod()
    
    a = "Hello"
    a.someMethod()
    
}

But be careful, as it hides nullity handling from your code.

2 Likes

What’s unusual with this method? If string is null return empty string, otherwise self string. Out of dependency of nullability.

I understood you. It’s really important purpose and if null will stretch through code it will be hard to trace them.
Thank you! Your answer really helped me! :grinning:

Topic can be closed.

I think a key point overlooked here is also that how a language is read is much more important than how it is written. The ?. operator (and !! operator) conveys what happens to the reader so the reader of code is not confused with what happens. The author of the code will also be forced to think out all scenarios. This is more work for authoring, and results in slightly larger code. But it results in better quality code (and less bugs) - the reason for this design isn’t because the compiler couldn’t figure it out on its own.

But you can see some languages (JavaScript, Perl, even Bash) that take a different approach. In general scripting languages (which are generally dynamically typed) are designed for small automation tasks and for those purposes ease of writing is more important than robustness. Statically typed languages tend to focus more on having the language help with avoiding bugs. The ? operator is an example.

3 Likes

I absolutely agree with you.

But what to do in next cases?

while (someValue().also {value = it} != null) {
    doSomething(value!!) //Value can't be null
}

if (value != null) {
    value?.doSomething() //Value can't be null
}

If you use local variables, you should not need !! operator, as Kotlin auto-casts to non-null type when it is safe. Exemple :


fun main(args : Array<String>) {
    val values = arrayOf("1", "2", null, "3")
    var i = 0
    var value : String? = null
    do { 
    	value = values.get(i++) ?: break
        doSomething(value)
    } while(value != null)
    
    if (value != null) replayLast(value)
}

fun doSomething(value: String) = println(value)

fun replayLast(value: String) = println("Again -> $value")

And also, in my opinion, it is exactly the case where a developer have to look back at what (s)he writes. If the compiler cannot easily guess nullity state, because assignation is embedded in a lambda, maybe the writer should take time to think about later readers / maintainers:

  • could my code be easier to understand ?
  • If it needs to be modified, will it be easy ? Would the lambda or the lack of local variables could make work more complicated ?
2 Likes

You example cases incorrectly assume that “value” can’t be null.

Here’s a a few examples that show you can’t trust null checks on non-captured values. The examples us a var property but they also apply if foo is a non-local val (aka, not captured).

fun main(args: Array<String>) {
//sampleStart
    foo = true

    if (foo != null) {
        doSomething()
        print("foo: $foo")
    }
//sampleEnd
}

var foo: Boolean? = true

fun doSomething() {
    // doSomething ...
    foo = null
}
fun main(args: Array<String>) {
//sampleStart
    foo = true

    if (foo != null) {
        println("foo: $foo")
    }
//sampleEnd
}

var foo: Boolean? = true 
    get() = field.also {field = null}
fun main(args: Array<String>) {
//sampleStart
    foo = true

    if (foo != null) {
        println("foo: $foo")
        println("foo: $foo")
        println("foo: $foo")
    }
//sampleEnd
}

var foo: Boolean? = true 
    get() = when(field) {
        true -> { field = false; true }
        false -> { field = null; false }
        null -> { field = true; null }
    }
fun main() {
//sampleStart
    foo = true

    if (foo == false || foo == null || foo == true) {
        // ...
    } else {
        println("Foo is not true, false, or null! 🤔")
    }
//sampleEnd
}

var foo: Boolean? = true 
    get() = when(field) {
        true -> { field = false; true }
        false -> { field = null; false }
        null -> { field = true; null }
    }
3 Likes