Is there a way to write code in such a way that th...
# announcements
p
Is there a way to write code in such a way that the kotlin analyzer wouldn't complain about these variables being nullable? I'm paraphrasing my code here, but it looks like this
Copy code
val a: String? = ...
    val b: String? = ...

    if (listOf(a,b).any { it == null }) {
      return
    }
    
    val l = a.length // compiler complains here
Basically I'm doing null checks in for a bunch of variables before doing something with them. Shouldn't the compiler know about
a
on the last line, not being null?
d
The compiler cannot know that the variables won't change between you checking them and you using them. They might be changed by a different thread or they might even have custom getters - That is why no smart casting takes place. If you want smart casting, you need to store them in a local variable first.
m
They’re
val
s, they cannot change. The problem here is that the
null
check is way too complex for the compiler to understand.
v
Pretty unlikely that a
val
is changed by anything, isn't it? The problem is, that the compiler is not that smart to see the connection between the elements you added to the list being all null-checked. If you would do
if (a == null || b == null) { return }
the smart-casting is done just fine.
p
yeah I expected that
c
val a: String = … ?: return
?
☝️ 2
p
but I was wondering if a) the compiler should know about it b) is there a way to write, maybe an extension method so that the compiler knows about it
m
There aren’t many options here. a)
if (a == null || b == null) return
b)
a?.let { b?.let { your code } }
p
@chermenin I guess that could be an option
m
@Pacane how many variables are we talking about? 😄
b
another option is just use
?:
in assignment
Copy code
val a = ... ?: return
val b = ... ?: return

val l = a.length
1
p
@Marc Knaup The current pattern we have in our code is like 10 variables per call site
we have many call sites where there's just a bunch of null checks like you showed in your a) solution
it's just a lot of repetitions... but yeah
I was wondering if we could refactor this thing to an extension method that'd do null checks on a list of variables instead
d
it's strange that you have 10 optionals around
p
they're coming from external services
m
It’s difficult to find the best course of action with a synthetic example. Can you provide a real example? Maybe there are different options.
p
+ I didn't write the code 😛 I'm just code reviewing
👌 1
s
If you have an occurring pattern of needing to check more than 1 nullable values for not being null, you could a bunch of overloaded
let
or
guard
functions. E.g.
Copy code
inline fun <A: Any, B: Any, R> let(a: A?, b: B?, block: (A, B) -> R): R {
    return if (a != null && b != null) block(a, b) else null
}

// overload this one for three params, 4 params, 5 params, etc, as much as you need)
p
right
here's one example we have
Copy code
var site = parsed.tags["site"]
            val asset = parsed.tags["asset"]
            val field = parsed.fields.first().fieldKey
            val value = parsed.fields.first().fieldValue

            if (site == null || asset == null || field == null || value == null) {
              continue
            }
in another call site, it's exactly the same thing, but we're checking if it's null or empty
m
Copy code
@OptIn(ExperimentalContracts::class)
inline fun anyNull(a: Any?, b: Any?, c: Any?): Boolean {
    contract {
        returns(false) implies (a != null && b != null && c != null)
    }

    return a == null || b == null || c == null
}
Usage:
Copy code
if (anyNull(a, b, c)) return
p
my provided solution was this
Copy code
fun<T> List<T>.anyNull() = this.any { it == null }
fun List<String?>.anyNullOrEmpty() = this.any { it.isNullOrEmpty() }
m
@Pacane why not use the
?: return
approach?
p
because in the second call site we want to check if null or empty
does that operator cover the empty string case?
d
v.takeIf { it.notEmpty() } ?: return null
m
You can make your own function and also check for empty string, yes
d
RE: "They are val's they cannot change":
val foo: Int get() = Random.nextInt()
Tada, your val is changing.
m
@Pacane
Copy code
@OptIn(ExperimentalContracts::class)
inline fun anyNullOrEmpty(a: String?, b: String?, c: String?): Boolean {
    contract {
        returns(false) implies (a != null && b != null && c != null)
    }

    return a == null || b == null || c == null || a.isEmpty() || b.isEmpty() || c.isEmpty()
}
@diesieben07 getters aren’t allowed for local
val
s 🤔 But delegation may be an issue.
d
Maybe I have not understood the problem here.
v
It is about local variable `val`s, not property `val`s
If you have a custom or open
getter
smart casting does not work anyway
And in case of delegation, smart casting also does not work due to the same reason
n
I feel like contracts are ultimately supposed to allow you to write a custom function that would solve this, similar to what Marc wrote
p
yeah and I've been trying to see if there's something in there that'd allow me to use an arbitrary amount of arguments instead of hard coding a fixed amount
n
Without contracts there are always going to be limits to what the compiler can infer; each time you extend the compilers ability to infer a certain condition, you can come up with a more complex condition where it fails
👍 1
m
@Pacane you can write the function for 20 parameters and simply use default arguments to allow for less than 20.
p
yeah that'd be a way
n
Contacts are experimental though, right? You can use them?
p
I wish there were some kind of construct in there that'd deal with lists lol
oh we're balls deep in optIns by now 😛
m
Using a list for that is weird. It just adds overhead for no real benefit 🤔
I also don’t care about experimental annotations in ANY project 😄
p
yeah, I might use the 20 arguments thingy
I was just wondering
n
Hmm contracts can't assert that a lists elements are non nullable?
You can't accept a x: List<String?> and say returns false implies that x is List<String> ?
@Marc Knaup the real solution is variadic generics but I wouldnt hold my breath :-)
😁 1
p
First time I hear about those 🙂