Safe call operator returning value other than null

Is there any safe call operator variant that short-circuits with some value other than null?

If not, I’d propose 2 (or 3) operators, like:

// 1: safe call operator that short-circuits with whatever you want, e.g.:
if (receiver?(true).equals("abc")) {
    …
}

val s = a?("1").b?("2").toString()

// 2: safe call operator that short-circuits with false instead of null, e.g.:
if (receiver?!.equals("abc")) {
    …
}

// 3: probably unlikely to be used frequently, so the first general version
// should suffice, but, possibly a safe call operator that short-circuits
// with true instead of null; not sure what syntax to use…

The keywords / syntax could be different, but the functionality should be as described.

Code other than literals should also be allowed in the first proposed operator; I just used literals to make things simple.

1 Like

I don’t like the idea. Especially the second operator is something you just overlook and then spend hours looking for the bug in your code. Why not just use the elvis operator instead?

if(receiver?.equals("abc") ?: true) {}
if(receiver?.equals("abc") ?: false) {}

Also what is this code supposed to do?

val s = a?("1").b?("2").toString()

Is b a function defined on String? If so what does the ? after the function name but before the params mean?

2 Likes

a is a variable.

b is a property of a.

"1" is the short-circuit value if a == null.

"2" is the short-circuit value if a.b == null.

toString() is called if a.b != null.

So a is of type String? Otherwise your code will not compile.

val temp = a?("1")   // either a is of type String or temp will be of type Any and the next line will not compile
val temp2 = temp.b?("2")
val s = temp2.toString()

Reasons not to use elvis:

  • if you want different null-receiver short-circuit values for different calls in a call chain.
  • why perform a second if condition when you already know that the receiver was null?
  • it’s nicer to see the short-circuit calue at the location of the short-circuit

Agreed this would sometimes be nice but you can already fix that with an extension function if you don’t like elvis

inline fun <T> T?.orElse(generator: () -> T): T = this ?: generator()

I’m not sure which second if condition you refer to. The elvis operator is as efficient as your new operator.

Maybe, but I would argue that my orElse function and the elvis operator is more readable. Maybe not the elvis operator in your call chain to generate s but in experience this is a rare case anyways.

1 Like

Option 1 is not a method call (I think you were mislead by the parentheses), it’s a safe call variant, which requires a call to a method. Thus, your first line would not be valid syntax:

val temp = a?("1")

The syntax requires a call, like:

val s = a?("1").toString()

Obviously, an elvis can be used instead of the above, but for long safe call chains, where you want a different null-receiver short circuit, then a single elvis won’t work.

Maybe the parentheses should be replaced by braces, like:

val s = a?{"1"}.toString()

The syntax would be:

val x: ReturedType = possiblyNullReceiver?{somethingThatEvaluatesToReturedTypeOrSubType).memberOrCallChainThatEvaluatesToReturedTypeOrSubType

My new operator immediately short-circuits with the desired value.

Existing safe call short-circuits with null, but then elvis must null check and substitute its right-hand side value, unless the compiler is smart enough to generate code with the elvis right-hand side value as the short-circuit value.

So I’m guessing the sub expressions in your example are:

val temp = a?{"1"}.b
val s = temp?{"2"}.toString()

I’m still not sure what the benefits of this over my orElse function is. That said I’m have less agains this operator than the second one that evaluates to false. I think ?! is too easy to miss and I can’t think of any real example where you would want to use false in a long call chain.

I think at the most we should add a orElse function to the standard library. Anything else would fall short of the minus 100 points rule anyways.

1 Like

Maybe I’m misunderstanding your orElse, but that wouldn’t seem to short-circuit a call chain, just generate a stand-in for a null value. My intention isn’t to continue the call chain, it’s to short-circuit it, just with a short-circuit return value other than null.

e.g.:

val b: X = …
val a: X? = …
val s: String = a.orElse(b).property

Will call property on ``a(ifa != null) or on b(ifa == null`).

Your orElse method requires a non-null of the same type as the receiver, whereas my operator would require something that evaluates to a String.

Ok, my bad. I thought you wanted to continue execution of the code after your operator. In that case, sry to say it, but I really hate the idea. It’s just too easy to overlook. In a long call chain you might just take it for a function (same mistake as I did earlier). Even the change to braces does not help, since they could just be a lambda.
If you want to short circuit the execution I suggest you do something like

val s = if(a == null) "1" else a.b?.toString() ?: "2"

It’s descriptive and easy to see that there are 2 different codepaths.

You still misunderstood: b is a property of a, not a separate variable.

See my comment from above:

Again, the syntax could be changed to make it less like existing constructs.

Woops, sry I did understand you. I just messed up my example code. I fixed it above.

1 Like

I believe this actually reduces readability instead of improving it. Your first example basically boils down to:

if (receiver == null || receiver == "abc") {
    //…
}

Your second example is basically a complicated way of witing:

if (receiver == "abc") {
    //…
}

I find this ridiculously readable, and terse enough. Do you really argue that your proposed expressions look better? Anything more complicated should really, really, be extracted into a method with a proper name anyway.

I fail to see a real-life example where better code hygiene doesn’t solve the problem up front.
Do you have real-life examples where this feature would be useful and improve readability?
It would help to see examples of code with/without the language feature that support the need for it.

I’m under the impression that you’re basically looking for a chainable ternary operator, and I don’t believe this will steer people towards more readable code.

IMO, so far, Kotlin has found the sweet spot between conciseness and verbosity to preserve readability. Too verbose, and you drown the meaning of the code in ceremony. Too terse, and the meaning becomes hidden in the syntax of the language.

3 Likes