Null safety comparison operators

I usually need to compare nullable variables, and I know we have some ways to handle this kind of comparison:

However, I am not happy with these approaches. Or maybe I don’t know an existing ideal solution?

What if Kotlin supports these operators

  • a ?< b nulls first less than
  • a ?> b nulls first greater than
  • a ?<= b nulls first less than or equal
  • a ?>= b nulls first greater than or equal
  • a <? b nulls last less than
  • a >? b nulls last greater than
  • a <=? b nulls last less than or equal
  • a >=? b nulls last greater than or equal

Maybe we don’t need the nulls first operators if by default we consider <, >, <=, >= as nulls first, to be consistent with compareValues function. But I am afraid that it’s not backward compatible and could break existing code, where we don’t know how null value is currently handled by implementer.

I think if compiler see these operators, it could automatically apply the logic below (or similar) for us:

// T?.compareTo(other: T?) for nulls first operators
if (a === b) return 0
if (a == null) return -1
if (b == null) return 1
return a.compareTo(b) // reuse current non-nullable compareTo()

// T?.compareTo(other: T?) for nulls last operators
if (a === b) return 0
if (a == null) return 1
if (b == null) return -1
return a.compareTo(b) // reuse current non-nullable compareTo()
3 Likes

In the original proposal above, if ? is the left most char, it means nullsFirst, regardless of the next chars.


Below are the alternative operators to my original proposal:

  • a ?< b compare with null, consider null is less than non-null value, then if both are not null check a < b

  • a >? b compare with null, consider null is less than non-null value, then if both are not null check a > b

  • a ?<= b compare with null, consider null is less than non-null value, then if both are not null check a <= b

  • a >=? b compare with null, consider null is less than non-null value, then if both are not null check a >= b

  • a <? b compare with null, consider null is greater than non-null value, then if both are not null check a < b

  • a ?> b compare with null, consider null is greater than non-null value, then if both are not null check a > b

  • a <=? b compare with null, consider null is greater than non-null value, then if both are not null check a <= b

  • a ?>= b compare with null, consider null is greater than non-null value, then if both are not null check a >= b

In the alternative proposal, if ? is on the left of <, it means, null is considered as smaller than non-null values.

Not sure which way is better for readers. For me, the original proposal is easier for my eyes because I don’t need to check the relationship between ? and < / >.

Actually, I think, it’s still backward compatible because otherwise the existing code should not compilable. But always having ? is more clear about a nullable comparison and explicitly about nulls first.

You probably know this already, but if you often use such operations in your project, you can create infix extension with a nullable receiver:

fun test(x1: Int?, x2: Int?) {
    if (x1 nullsFirstLT x2) {
        
    }
}

infix fun <T : Comparable<T>> T?.nullsFirstLT(other: T?): Boolean = when {
    this === other -> false
    this == null -> true
    other == null -> false
    else -> this < other
}

// alternatively: = this !== other && other != null && (this == null || this < other)

@broot Hi, thanks for your suggestion!
Yes, I have all cases as function extensions in my projects. But I am still not really happy with it.
Reading code with nice operators look more natural I think.