Kotlin null safety? - kotlin

Let's have a function foo and a class Bar:
fun foo(key: String): String? {
// returns string or null
}
class Bar(x: String, y: String) {
// ...
}
Now, let's have the code:
val x = foo("x")
val y = foo("y")
if (x.isNotEmpty() && y.isNotEmpty())
return Bar(x, y)
The problem is that this code will not compile. Since it needs the Bar(x!!, y!!).
However when I replace the function with its content, !! are not needed.
val x = foo("x")
val y = foo("y")
if ((x != null && x.length() > 0) && (y != null && y.length() > 0))
return Bar(x, y)
Why it is not possible to resolve the null check from the function .isNotEmpty()?

This is possible in theory, but it would mean that either
1. The declaration of isNotEmpty() must convey to the compiler the fact that x is guaranteed to be non-null if the result is true
2. A change to a body of any function may cause its call sites to fail to compile.
Option 2 is definitely unacceptable. Option 1 requires a rather expressive mechanism in the type system, which we decided not to add at the moment, because it is likely to complicate things for the user.
We are planning to support something like this with inline functions, but it's still under consideration.

Related

Kotlin - Type mismatch: inferred type is Any? but Boolean was expected

I'm trying my hands on Kotlin. Being from a Python background is really giving me a tough time to get the knack of the Kotlin syntax. I'm trying to do a simple dictionary (Mutable Map) operation. However, its giving me exceptions.
This is what I tried. Kotlin compiler
Adding the code snippet for reference.
fun main() {
val openActivityMap = mutableMapOf<String, MutableMap<String, Any>>()
val packageName = "amazon"
val currentTime = 23454321234
if(openActivityMap.containsKey(packageName)){
if(openActivityMap[packageName]?.get("isAlreadyApplied")){
if((openActivityMap[packageName]?.get("lastAppliedAt") - currentTime) > 3600){
openActivityMap[packageName]?.put("isAlreadyApplied", false)
}
}
else{
openActivityMap[packageName]?.put("isAlreadyApplied", false)
}
}
}
I'm a bit late to the party, but I'd like to point out another solution here.
As I commented on the OP, heterogeneous maps with fixed string keys like this are usually better expressed with classes in Kotlin. For instance, in your case, the class for your main map's values could be the following:
data class PackageInfo(
var isAlreadyApplied: Boolean,
var lastAppliedAt: Long,
)
(you could obviously add more properties if need be)
This would save you all the casts on the final values.
Another point I'd like to make is that if you access the value for a key anyway, you don't need to check up front the existence of the key with containsKey. Maps return null for keys that are not associated with any value (this is why you need to check for null after getting the value).
The compiler cannot see the correlation between containsKey and the subsequent get or [] access. However, it's smart enough to understand a null check if you simply get the value first and then check for null.
This always applies unless you want to tell the difference between keys that aren't in the map and keys that are in the map but associated null values (which is quite rare).
All in all, I would write something like that:
fun main() {
val openActivityMap = mutableMapOf<String, PackageInfo>()
val packageName = "amazon"
val currentTime = 23454321234
val packageInfo = openActivityMap[packageName]
if (packageInfo != null) { // the key was found and the value is smart cast to non-null in the next block
if (packageInfo.isAlreadyApplied) {
if ((packageInfo.lastAppliedAt - currentTime) > 3600) {
packageInfo.isAlreadyApplied = false
}
} else {
packageInfo.isAlreadyApplied = false
}
}
}
data class PackageInfo(
var isAlreadyApplied: Boolean,
var lastAppliedAt: Long,
)
I would recommend writing tests first and working in small increments, but this should fix your compilation issues:
fun main() {
val openActivityMap = mutableMapOf<String, MutableMap<String, Any>>()
val packageName = "amazon"
val currentTime = 23454321234
if(openActivityMap.containsKey(packageName)){
if(openActivityMap[packageName]?.get("isAlreadyApplied") as Boolean){
if((openActivityMap[packageName]?.get("lastAppliedAt") as Long - currentTime) > 3600){
openActivityMap[packageName]?.put("isAlreadyApplied", false)
}
}
else {
openActivityMap[packageName]?.put("isAlreadyApplied", false)
}
}
}
EDIT: Also I prefer to avoid nullable variables and mutable objects in general, but I suppose there's an exception to every rule.
Couldn't you just declare your Map<String, Any> to return a Boolean instead of Any? So,
val openActivityMap = mutableMapOf<String, MutableMap<String, Boolean>>()
It looks like you're trying to use your second Map to store both Booleans and Ints, which is complicating the logic. You'll need to typecast if you decide to approach it without Typing.
There's a problem with the 2 statement below
if(openActivityMap[packageName]?.get("isAlreadyApplied"))
if((openActivityMap[packageName]?.get("lastAppliedAt") - currentTime) > 3600)
As we all know, an IF statement requires a boolean value for it's param. The types of both statement are unknown at compilation time as they are of a Generic type, Any. As such,
openActivityMap[packageName]?.get("isAlreadyApplied") could be a null or of type Any (Not Boolean).
openActivityMap[packageName]?.get("lastAppliedAt") could be a null or of type Any (an Int was expected here for computation).
This would throw compilation errors as the compiler does not know the types to go with. What could be done is to cast to it's proper types.
Solution
openActivityMap[packageName]?.get("isAlreadyApplied") as Boolean ?: false
((openActivityMap[packageName]?.get("lastAppliedAt") as Int ?: 0) - currentTime)
Giving a default value if it's null.
maybe you can try something like this
if (openActivityMap.containsKey(packageName)) {
val packageMap = openActivityMap[packageName]!!
val applyRequired = (packageMap["lastAppliedAt"] as Long - currentTime) > 3600
packageMap["isAlreadyApplied"] = packageMap.containsKey("isAlreadyApplied") && !applyRequired
}
btw. do you really want to have lastAppliedAt to be in te future? otherewise it will never be > 3600

Why is my Kotlin comparable not makign correct equality comparisons?

I am currently trying to make a comparable object and working on the compareTo() function, for which I wrote the following code
class InfoAcad(e: String, m: String, c: Int): Comparable<InfoAcad> {
override operator fun compareTo(other: InfoAcad): Int {
if (this.e < other.e) return -1
if (this.e > other.e) return 1
if (this.e == other.e && this.m < other.m) return -1
if (this.e == other.e && this.m > other.m) return 1
return 0
}
}
The idea is that e is an ID number inputted as a string, which always follows the format XX-XXX where every X character is an integer between 0 and 9, and m is a course code following the format LL-XXX where each L character is a capital letter between A and Z and the X characters are integers between 0 and 9 like in the ID numbers. The objects are first compared by their ID number, and if the ID numbers are equal they are then compared by the course code, if both values are the same then the objects are equal, the c parameter is not taken into account in the comparison. I found out yesterday that I could compare strings directly in Kotlin in < and > relations, so I decided to try using that to make the task of comparing the InfoAcad objects a bit easier on myself, however when I make a main function to test the comparisons, the equality always returns a false value independently of what is in the string values of the InfoAcad objects. Here's said main function:
fun main() {
var A = InfoAcad("18-10125", "CI-2526", 3)
var B = InfoAcad("18-10125", "CI-2526", 5)
println("A = B: " + (A == B).toString()) //true
println("A < B: " + (A < B).toString()) //false
ptintln("A > B: " + (A > B).toString()) //false
}
When I change characters in the ID and course code values the inequality relations work just as intended, so what could be causing the equality relation to always return false? I appreciate and thank any responses in advance.
Note: I have also tried giving A and B the same c value, the equality part still returned false.
Override the equals function as well, or use a data class.
compareTo is only used for the < and > operators. The == operator is implemented by the separate equals function.
You can find the available operators, and the functions you need to override for each, in the Operator overloading section of the Kotlin docs.
If you don't override the equals function, the default behaviour is for it to use object identity. That means that two different objects, even if they contain the same fields, will never be considered equal.
There is however a nice shortcut for what you want to do! Kotlin will automatically generate an equals function for you if you make your class a data class. It's a good fit for classes like yours, whose main purpose is to hold data.
Because == and != translates to a call to equals(other: Any?): Boolean method, from kotlinlang
Expression Translated to
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))
These operators only work with the function equals(other: Any?): Boolean, which can be overridden to provide custom equality check
implementation. Any other function with the same name (like
equals(other: Foo)) will not be called.

How to rewrite this "check, assign and return" pattern?

Is there any clearer, more idiomatic way to rewrite this "check, assign and return" function?
fun getX(): Y {
if (X == null) {
X = ...
}
return X!!
}
Edit: I know of Elvis operator but the problem is that although I can save on few lines with syntax sugar
fun getX(): Y {
X = X ?: ...
return X!!
}
I'm not changing the semantics of the code. I need to force casting anyway (!!). So it's the same, just three lines shorter.
Edit: X is a property on some object, and I need to update it.
You can move assignment to the right hand side of the elvis operator using also function:
fun getX() = x ?: valueIfXisNull.also { x = it }
I think you are looking for by lazy i.e. a property that is initialized the first time it is called. See Delegated Properties and lazy initialization tutorial
val x :String by lazy {
"a"
}
or in your case:
val z : Y by lazy {
y!!
}
You might also want to have a look at lateinit-properties:
lateinit val y : Y
You can use the Elvis Operator: ?:
If the expression to the left of ?: is not null, the elvis operator returns it, otherwise it returns the expression to the right.
You can update your variable with the elvis operator then return the result.
fun getX(): Y {
x = x ?: valueIfNull // If x is null, x becomes 'valueIfNull'.
return x!! // x cannot be null, return it.
}

A better way to assign only if the right side is not null?

In Kotlin, I want to do an assignment only if another variable is not null (otherwise, no op). I can think of two succinct ways:
fun main(args: Array<String>) {
var x: Int? = null
var n = 0
// ... do something ...
x?.let { n = it } // method 1
n = x ?: n // method 2
}
However, they don't feel succinct enough, given the frequency I have to do them. The first method seems an overkill. The second method is nagging in requiring an expression after ?:.
I suspect there must be a better way, something like n =? x? Or n = x?? Is there?
Try infix to 'simulate custom infix operations'
// define this
infix fun <T > T.assignFromNotNull(right: T): T = right ?: this
///////////////////////////////////////////////////////////
// Demo using
// Now, Kotlin infix-style
fooA assignFromNotNull fooB
barA assignFromNotNull barB
bazA assignFromNotNull bazB
// Old code, Java if-style
if (fooB != null) {
fooA = fooB;
}
if (barB != null) {
barA = barB;
}
if (bazB != null) {
bazA = bazB
}
There's the following:
val x: Int? = null
val n: Int = x ?: return
This compiles perfectly fine, even though n may not be assigned. Even calls that use n after its 'assignment' are allowed, e.g. println(n), because the compiler only knows that n is Int and that's OK. However, any lines following the assignment will never be called, because we return from the scope. Depending on what you want, that's a no-op. We can't continue because n couldn't be assigned, so just return.
Another option is val n: Int = x!! which will throw a NullPointerException if x == null that should be handled elsewhere. I don't recommend this practice, because Kotlin offers cleaner methods to handle nullability.

null to listOf(), not null to listOf(value) in Kotlin in one liner?

Let f() return a nullable value.
What I want to do is that
if f() is null, get an empty list,
else if f() is not null, get a list of the single item value.
In Scala, we can do something like this:
Option(f()).toList
or more verbosely
Option(f()).map(v => List(v)).getOrElse(List.empty)
In Kotlin, there is no Option (assuming no Funktionale library), and null does not have toList() unlike (None: Option) in Scala.
We have the Elvis operator, but null will be inside the listOf() function, so it will be
listOf(f() ?: /* What can I do here? */)
What we want for null is listOf(/*no argument */), but the Elvis operator requires an argument, so listOf(f() ?: ) will result in a compile error.
At least we can do
val v = f()
if (v == null) listOf() else listOf(v)
but it is a two liner.
Is there some expression for this?
Where I will use this expression is in the class's primary constructor default argument, so if it is not a one liner, it will be enclosed in brackets, so something like this:
class A(
val p1: List<V> = run {
val v = f()
if (v == null) listOf() else listOf(v)
},
val p2: ... = ...,
...)
This looks pretty ugly, isn't it?
EDIT
As #Naetmul pointed out, listOfNotNull(f()) is syntactically better to what I originally posted below, and also takes a variable number of arguments, for example
val myList = listOfNotNull(f(), g(), h())
will return a list of all the results that were not null.
I would use let here.
val myList = f()?.let { listOf(it) } ?: emptyList()
Use a ?. safe call on the return value of f(), then use let to run a code block. If f() is null, it won't run this block of code, resulting in a null value. Then we use the ?: elvis operator to fall back to an empty list.
Here it is broken up into several lines for a better understanding
val myValue = f()
val myList: List<Any>
if (myValue != null) {
myList = listOf(myValue)
} else {
myList = emptyList()
}
For this specific question, I can do
listOfNotNull(f())