How to prevent implicit generalization casts when using generic types in Kotlin? [duplicate] - kotlin

This question already has an answer here:
Force type parameter to be invariant at use-site when it is covariant at declaration site
(1 answer)
Closed 1 year ago.
Minimal example:
fun <T> Iterable<T>.find2(elem: T) = this.find { it == elem }
Here, T is used to both denote the type of the Iterable, as well as the type of elem. It seems fine, however, a statement like this is syntactically correct, but doesn't make semantic sense:
listOf(1, 2, 3).find2("foo")
I assume this works because T resolves to Any.
I'm aware of the solution to explicitly state the type of the function:
listOf(1, 2, 3).find2<Int>("foo")
In this case, the compiler reports an incompatibility error, and rightfully so. However, this doesn't seem to be the solution, since it requires to explicitly declare the type, and will not report an error if forgotten (which makes bugs likely).
Is there a way to "constrict" the type <T>, so that, if, for example, the receiver is of type Iterable<Int>, the parameter must also be Int? In other words, is there a way to prevent the implicit cast to Any?

In the statement
listOf(1, 2, 3).find2("foo")
There are actually two types that need to be resolved,
T1 in listOf<T1> and T2 in Iterable<T2>.find2,
and have the following constraints
T1 is the same as T2, because listOf returns List<T1>, which is Iterable<T2>, for find2
1, 2 and 3 should be assignable to T1
"foo" should be assignable to T2
Therefore, the type has to be Any.
We usually explicitly declare the type of the list since it is the source,
listOf<Int>(1, 2, 3).find2("foo")
Or separate it into two statements
val listOfInt = listOf(1, 2, 3)
listOfInt.find2("foo")

Related

What is the difference in these way of creating a list in Kotlin ? My result1 is valid but result2 is invalid

val list1 = listOf(1,2,3,4,5,6,7,8,9)
val list2 = listOf(1..9)
/*
my result1 is valid but result2 gives error
*/
val result1= list1.filter{i -> i>2}
val result2 = list2.filter{i -> i>2}
listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
is a list of nine numbers — specifically, Ints.
However, 1..9 is a range object — specifically, an IntRange.  So:
listOf(1..9)
is a list that contains a single object.
list1.filter{ i -> i > 2 }
is valid, because filter() applies its lambda to each Int in the list — and the > operator is defined between two Ints.  However:
list2.filter{ i -> i > 2 }
is not valid, because the lambda is applied to the IntRange, and you can't compare an IntRange to an Int.
Your IDE and compiler should give you several clues about this.  First, if you hover over list1 and list2 you'll see the types that the compiler infers for them: List<Int> and List<IntRange>, respectively.
And not only does the IDE underline { i -> i > 2 }, it also shows the > in red.  Hovering over that shows the error ‘Unresolved reference. None of the following candidates is applicable because of receiver type mismatch…’  That's a bit cryptic, but you should get to know it, because it's quite common and very useful.  It's telling you that you're trying to call a function/method/operator that's not defined for the type(s) you have.  In this case, > (which is equivalent to compareTo()) is defined for two Ints, two Longs, two Strings, etc., but not for an IntRange and an Int.
So the compiler doesn't know what return type it should have, and temporarily assumes Unit — but filter() needs a lambda returning a Boolean, which is why the whole lambda is underlined with a ‘Type mismatch’ error.
As Tenfour04 says, you can convert a range into a list if you need to (using the toList() method).  But in practice, that's not needed very often because there's usually a better approach.  (A range takes much less memory than most lists, and is therefore quicker to iterate through; and it implements Iterable so you can already do most list-type things with it anyway.)

What different StringBuilder and StringBuilder! in kotlin? [duplicate]

This question already has answers here:
Single exclamation mark in Kotlin
(7 answers)
Example of when should we use run, let, apply, also and with on Kotlin
(6 answers)
Closed 2 years ago.
In the code below. I found in Intellij Idea compiler that val a and val b by default are "val a: StringBuilder" & "val b: StringBuilder!"
what is the difference between the two? What's the difference between StringBuilder and StringBuilder! ? Thank you :)
fun main(){
val a = StringBuilder().apply { // by default is val a : StringBuilder
append("Hello ")
append("from me")
}
println(a)
val b = StringBuilder().run { // by default is val b : StringBuilder!
append("Hello ")
append("from me")
}
println(b)
}
The ! indicates a platform type. It means that the compiler can't tell whether the type is nullable or not, because it comes from Java (or another JVM language), which doesn't make the distinction between nullable and non-nullable types, and doesn't have an annotation (#Nullable or #NonNull) to indicate that.
As a result, the compiler won't be able to make its usual null checks, so you should take care.
If you know (from the documentation, or looking at the Java code, or whatever) whether the value could be null or not, it's a good idea to specify the type explicitly (as either nullable with a trailing ?, or non-nullable without).
In this case, the difference is that apply() returns the value it was called on; that's all Kotlin, so the compiler knows its type. However, run() returns the last value in the lambda, which is the result of the last append() call. That method is defined in Java (since StringBuilder is part of the Java standard library), so the compiler can't tell whether it's nullable or not. But it's clear from the documentation that the method simply returns the StringBuilder it was called on, and so cannot be null. So for safety, you could specify an explicit StringBuilder type (i.e. non-nullable) for b.

Why should I assign the data type of variables in Kotlin if giving it values initially it is capable of inferring the data type of the variable

As mentioned in the code provided. My question is if there is any advantage by mentioning the data type early on when I am just giving the variable it's value. As said in line 2 it can understand that it is int type.
val a: Int = 1 // immediate assignment
val b = 2 // `Int` type is inferred
val c: Int // Type required when no initializer is provided.
A few reasons you might want to specify a type explicitly:
You want a supertype of the value (perhaps so you can to reassign it later), e.g.:
var myList: List<String> = ArrayList<String>()
You want to protect against changes to other code (especially if it's outside your control), e.g.:
val x: MustBeThisType = SomeLibrary.getValue()
(That would give an error if SomeLibrary.getValue() ever changes to returns something other than MustBeThisType or a subtype.)
You want to avoid an explicit numeric conversion, e.g.:
val x: Long = 2
instead of:
val x = 2.toLong()
You want to make it very clear to someone reading your code (especially if that might not be in an IDE).
As Michael says, you may need to specify the nullability of types returned from Java, e.g.:
val x: String = someJavaClass.getAString() // Never returns null
None of those is particularly common in my experience, though.

Cast Any to Array in Kotlin

I'm initializing a class by loading data from a Map<String, Any> in Kotlin. As this Map is gleaned directly from JSON, I don't know for certain that any given key exists, or that its value is of the type I expect. To unpack this Map safely I'm doing the following, which appears to work perfectly:
a = rawData["A"] as? String ?: ""
Some of this data is in further nested JSON, which I'm unpacking to Arrays; I've tried to do this in the same way:
b = rawData["B"] as? Array<String> ?: arrayOf<String>()
However, when I attempt this using an array (as above) IntelliJ kicks up a fuss, saying
Warning:(111, 30) Kotlin: Unchecked cast: Any? to Array<String>
Is this just the IDE getting itself in a twist or is this method genuinely unsafe for Arrays despite being seemingly perfectly safe for other types?
For any future readers of this question, to expand on the accepted answer with a solution:
To safely cast Any to an array of a particular type in Kotlin, you have to first cast to an untyped array (see zsmb13's answer above for why), and then filter that array to the desired type.
For example, to cast input: Any to an array of String instances, you would call:
val inputAsArray = (input as? Array<*>)?.filterIsInstance<String>()
I was ready to call this a bug, because Array is a reified type, meaning its generic parameter can actually be checked at runtime (unlike a List, for example). I've tried looking to see if it's been filed yet, and it turns out the compiler is actually right to show you a warning. As you can see in the response to this issue, there's still a nullability problem with casts of this kind.
val a = arrayOf("foo", "bar", null) as Array<String>
println(a[2].length)
Arrays like the one in this example are successfully cast (using as, they don't throw an exception, using as?, they don't return null), however, the cast can not ensure that this is an Array<String>, only that it's an Array<String?>.
This means that you can later read null values from a variable that is typed as an Array<String> after the cast, with no further warnings from the compiler.

Fix generic type to the type of the first parameter

I want to write an extension function which will be available on any type and accept parameter of the same type or subtype, but not a completely different type.
I tried naive approach but it didn't work:
fun <T> T.f(x: T) {
}
fun main(args: Array<String>) {
"1".f("1") // ok
"1".f(1) // should be error
}
It seems that compiler just uses Any for T. I want T to be fixed to receiver type.
The only way to do it requires telling the compiler what you want.
fun <T> T.f(x: T) {
}
In order to use it, you have to tell Kotlin what you want the type to be.
"1".f<String>("2") // Okay
"1".f(2) // Okay (see voddan's answer for a good explanation)
"1".f<String>(2) // Fails because 2 isn't a String
"1".f<Int>(2) // Fails because "1" isn't an Int
When you call fun <T> T.f(x: T) {} like "1".f(1), the compiler looks for a common super-type of String and Int, which is Any. Then it decides that T is Any, and issues no error. The only way to influence this process is to specify T explicitly: "1".f<String>(1)
Since all the checks are performed by the compiler, the issue has nothing to do with type erasure.
Your issue is like saying "John is 3 years older than Carl, and Carl is 3 years younger than John" ... you still don't know either of their ages without more information. That's the type of evidence you gave the compiler and then you expected it to guess correctly. The only truth you can get from that information is that John is at least 3 years old and Carl is at least 1 day old.
And this type of assumption is just like the compiler finding the common upper bounds of Any. It had two strong literal types to chose from and no ability to vary either. How would it decide if the Int or String is more important, and at the same time you told it that any T with upper bounds of Any? was valid given your type specification. So the safe answer is to see if both literals could meet the criteria of T: Any? and of course they do, they both have ancestors of Any. The compiler met all of your criteria, even if you didn't want it to.
If you had tie-breaking criteria, this would work out differently. For example, if you had a return type of T and a variable of type String receiving the value, then that would influence the decision of Type inference. This for example produces an error:
fun <T: Any> T.f2(x: T): T = x
val something: String = "1".f2(1) // ERROR
Because now the type T is anchored by the "left side" of the expression expecting String without any doubt.
There is also the possibility that this could also be an type inference issue that is not intended, check issues reported in YouTrack or add your own to get a definite answer from the compiler team. I added a feature request as KT-13138 for anchoring a specific type parameter to see how the team responds.
You can fix T to the receiver type by making f an extension property that returns an invokable object:
val <T> T.f: (T) -> Unit
get() = { x -> }
fun main(vararg args: String) {
"1".f("1") // will be OK once KT-10364 is resolved
"1".f(1) // error: The integer literal does not conform to the expected type String
}
Unfortunately "1".f("1") currently causes an error: "Type mismatch: inferred type is String but T was expected". This is a compiler issue. See KT-10364. See also KT-13139. You can vote on and/or watch the issues for updates. Until this is fixed you can still do the following:
"1".f.invoke("1")
/* or */
("1".f)("1")
/* or */
val f = "1".f
f("1")