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.
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?
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()
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.
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.
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.
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.