`foo?.bar.yolo.stop()` - Mixed Nullable & Non-Nullable Call Chain


#21

If I am not mistaken, you are wrong. It works this way in Groovy based on Groovy truth concept. But in kotlin elvis operator works only on nullability. There was a huge thread about it recently.


#22

updated my code, thx for pointing it out


#23

At this point, I’m open to more discussion on this feature but I’m fairly certain this is an approach that can work out well.
So if we can please come up with a Code Example to counter my proposal, that would be great.


#25

What about:

foo?.run { bar.yolo.stop() }

This has the advantage of being valid Kotlin and avoiding all the pitfalls discussed. In the meantime there is nothing to stop the compiler from generating exactly the same code in both cases.


#26

Repeating ?. means less magic and implicitness. a?.b?.c could naturally be read as (a?.b)?.c. And if you really need this, you can always do something like a?.with{b.c.d}.


#27

Yes, I have indeed provided the same example in the original post. They are workarounds but foo?.bar.yolo.stop() is straightforward which is what I’m pushing for.


#28

This one shows how Null Conditional Operator works in C#:

http://www.informit.com/articles/article.aspx?p=2421572

“This example shows how much cleaner code becomes by using the null conditional operator. The more lengthy form is quite a bit more verbose. While this example used the ?. operator on each member access, that’s not required. You can freely mix the null conditional operator with normal member access. If the above assignment were used in a routine where p had already validated to be non-null, you could assign the spouse’s name as follows:”

var spouseName = p?.Spouse.FirstName;

#29

It is definitely not a workaround. This is idiomatic Kotlin, and a very concise way to write down what your intention is. Write down what is happening in this code fragment in plain English, and you will see how concise it is.

Yes, there are more concise ways:

val resultOfStop = foo?.bar?.yolo?.stop()
val resultOfStop = foo?.bar.yolo.stop() // Your suggestion
val resultOfStop = foo.bar.yolo.stop() // Another suggestion in this topic
val resultOfStop = foo bar yolo stop // I hate punctuation, and the compiler can figure out what the possibilities are
val r = f b y s // Man, do I hate typing. And there is only 1 property starting with "b", so the compiler can still figure it out

Language design is not only a character counting exercise. Other reasons include: having an easy to read and understand language, so large-scale projects can be written and maintained in it; consistent semantics; a type system that helps prevent bugs.

There are plenty of developers who have trouble understanding Kotlin (see the confusion about ?: operating on booleans above). Let’s not make it even more difficult, just to save a couple of characters.

I am absolutely not against people proposing language improvements, but please stop calling nice, short code workarounds.


#30

@madmax1028: You put the wrong code snippet below that text. The correct snippet is:

var spouseName = p.Spouse?.FirstName;

The difference with C# that is relevant in that article, is that C# allows you to use . on a nullable type. If the value actually is null, then a NullReferenceException exeception will be thrown. Kotlin does not allow this, and forces you to handle the null properly.

Note that the C# semantics are different than what is proposed in this thread, but would work for the example in the original post becasue bar can never be null. But if bar can be and is null (and foo is not null), then the results would be:

// Kotlin using proposed syntax
val resultOfStop = foo?.bar.yolo.stop() // = null
// C#
val resultOfStop = foo?.bar.yolo.stop() // NullReferenceException

#31

We have a function in the standard library: String?.isNullOrEmpty(). It can be called on a nullable receiver and returns true if the receiver is null or an empty string.

Now consider the following example:

class Person(val name: String)
val person: Person? = null

println(person?.name.isNullOrEmpty())   // prints true
println(person?.name?.isNullOrEmpty())  // prints null, isNullOrEmpty isn't called

Does this proposal change the behavior of this example?


#32

Could you provide an example how null is equal to false when using ?: operator?


#33

I misread something somewhere. In Groovy apparently null == false, but not in kotlin.


#34

I never realized extension functions and properties would work this way.
Until now, I thought everything on RHS of ?. is not invoked if LHS is null. But seeing as how extensions are invoked regardless (if further down the chain), this proposal will indeed break the current expected output. As this will be a breaking change, I’m not any more in favour of this proposal.

Thank you for the concrete code example. Really helps to argue objectively.

Edit: An interesting thing was, when I presented this problem to my peers they automatically assumed it’s already foo?.bar.yolo.stop(). Somehow at first glance it seems more intuitive.


#35

Indeed, basically every ?. is evaluated individually and just skips the method call after the . (replacing the call with null). Of course the compiler can optimize this in case it knows that the method itself will not return null (and you don’t have an extension call on a nullable type) and create the logic that has a single if statement after foo.


#36

Not having read all of the comments;

I think this change would introduce an inconsistency. If we break it down:

foo?.bar?.yolo?.stop()

where bar and yolo properties are non-nullable, can be broken down like so

var step1 = foo
var step2 = step1?.bar
var step3 = step2?.yolo
var step4 = step3?.stop()

It is true that subsequent null checks are not necessary, and the compiler might be able to figure that out,
but the intermediate types are still nullable. If the change were implemented, you wouldn’t be able to break it down like this without the question marks, so it wouldn’t make sense.

It makes much more sense to use the .run() approach, as it creates a bigger and more explicit “code block” to evaluate if foo is not null. It would make sense if you could do something like this as well:

foo? { .bar.yolo.stop() }

to explicitly show where the resulting non-null-check’s block should end. This doesn’t make sense at all though with the current language design, where a lot of features are not intrinsically a part of the language. This would require a very weird change, and I’m not suggesting that it should be made. But potentially, the functionality of ? could be expanded in a way that is consistent to allow explicitly denoting the bounds of the expression that is evaluated after the non-null-check.

It’s essentially about being able to put a part of the expression in parentheses.


#37

I like having the ? there in general; they make nullability explicit.

However, reading nonNull()?. is just confusing. Apparently, the Kotlin compilers works strongly left-associatively here:

((foo?.bar)?.yolo)?.stop()

That makes sense; but the reader might read more procedurally:

At each ?, either the value is not null and we continue, or it isn’t and we skip to the end of the chain.

People like me who have programmed non-trivial amounts of Swift will certainly do that. Not only that; I think

foo?.bar.yolo.stop()

carries more useful information, especially if combined with ?: after this expression. Which nullability are we dealing with here? Right, foo can be null, but the rest never will be.

There is a slight complication (that Swift doesn’t have): we can define extension functions on Any?. Say we defined fun Any?.stop() – then the left-associative and the skip-chain interpretations are not equivalent.
But the compiler knows about such functions and could complain if it led to ambiguous situations.

Either way, changing this would be a breaking change due to such extension functions, unfortunately. :confused:


#38

Case in point, Java expert Trisha Gee at JBCNConf’18:

if (customer?.name?.startsWith("A") == true) { ... }

Then I can use this question mark to basically chain my way through customer, name, startsWith, and if there’s null anywhere there it would just ignore it […] and won’t follow the chain through.

In the context of this discussion, yes: this is how we want to understand ? but it’s a little more subtle than that – in fact, it does follow the chain through! I think the point we’re trying to make above is that the subtlety is surprising.

There’s also the issue I laid out in Is the idiom for checking nullable booleans helpful? in this snippet.