Why does mapOf() with multiple types of keys return a Map with an `out` key parameter? - kotlin

In Kotlin 1.7.0,
mapOf("s" to "s2", 0 to 5)
gives this response in the ki REPL:
res1: Map<out Any, Any> = {s=s2, 0=5}
My question is, why is the key parameter out (covariant)? Normally I might not care, but I'm trying to understand Kotlin generics, and specifically how variance works.
According to the docs, the signature for mapOf() is
fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>
I can understand that in order to cover both String and Int types, the K type parameter has to be Any. But why out? And why out for K and not for V?
If we try a mutable map, we get out for both:
mutableMapOf("s" to "s2", 0 to 5)
gives
MutableMap<out Any, out Any>
This makes even less sense to me. At least K is consistent with V in both being out. But now we can't add or change any of the mappings in our mutable map, because keys and values are both of a "producer" type:
val m2 = mutableMapOf("s" to "s2", 0 to 5)
[10] m2.put(3 to 6)
ERROR Type mismatch: inferred type is Pair<Int, Int> but Nothing was expected (Line_11.kts:1:8)
ERROR No value passed for parameter 'value' (Line_11.kts:1:14)
[15] m2[3] = "fi"
ERROR The integer literal does not conform to the expected type CapturedType(out Any) (Line_16.kts:1:4)
ERROR Type mismatch: inferred type is String but CapturedType(out Any) was expected (Line_16.kts:1:9)
We can fixed this by explicitly specifying the type parameters of our mutable map:
val m3: MutableMap<Any, Any> = mutableMapOf(3 to 4, "a" to "b")
But I'm wondering why these parameters in the inferred type are out. Is this intentional, or is it an unfortunate side effect of the generics system?
Update:
When I try something similar in the IDE, the out annotation is apparently not generated:
val myMap = mutableMapOf("s" to "s2", 0 to 5)
myMap[3] = 7
The IDE says the type of myMap is MutableMap<{Comparable*> & java.io.Serializable}, {Comparable*> & java.io.Serializable}>, as Tenfour04 also said in the comments. The ability to assign myMap[3] = 7 shows that the key is not annotated out. So I'm guessing that the out comes from something unique to the ki shell, or from Kotlin 1.7.0 (whereas the IDE seems to be running Kotlin 1.7.20).
Update 2:
I've now run this in a Kotlin scratch file in Android Studio (under Kotlin 1.7.20, on JVM). The following are local variables in main():
val m = mapOf("s" to "s2", 0 to 5)
printType(m)
val mm = mutableMapOf("s" to "s2", 0 to 5)
mm[3] = 7
printType(mm)
I'm using the printType() function that #broot linked to in comments:
import kotlin.reflect.typeOf
inline fun <reified T> printType(obj: T) {
println(typeOf<T>())
}
The results:
kotlin.collections.Map<out kotlin.Any, kotlin.Any>
kotlin.collections.MutableMap<out kotlin.Any, out kotlin.Any>
These are the same results I described at the beginning of the question.
This seems to me to show that the out annotation is not coming from something specific to the ki shell, nor from Kotlin 1.7.0.

Related

Kotlin sortedMap compared by the Pair value not the key

I found this statement
sortedMapOf(compareBy<String> { it.length }, Pair("abc", 1), Pair("c", 3), Pair("bd", 4), Pair("bc", 2))
So it sorts first by the length of the key. However what I need is a sort by the value of the map's pairs.
Therefore I tried this:
mapOf("eat" to 11.0, "sleep" to 50.0).toSortedMap(Comparator<Pair<String, Double>> { o1, o2 -> o1.second.compareTo(o2.second) })
For some reason the part Comparator<Pair<String, Double>> { o1, o2 -> o1.second.compareTo(o2.second) } gives me an error.
My IDE (IntelliJ) tells me this:
Type mismatch.
Required: Pair<String, Double>
Found: String
I don't get it where am I passing a String. All I can see is me using the Pair.
Key in mapOf("eat" to 11.0, "sleep" to 50.0) is of type String, hence toSortedMap is expecting you to provide a comparator that takes string as argument.
To make the code runnable, you'll have to make a new map where the key is of type Pair<String, Double>.
mapOf("eat" to 11.0, "sleep" to 50.0)
.map { Pair(it.key, it.value) to it.value }
.toMap()
.toSortedMap { o1, o2 -> o1.second.compareTo(o2.second) }
This is of course strange code, but it should answer the question.
SortedMaps are sorted on their keys - you can't sort them on their values. And here's the signature of sortedMapOf():
fun <K, V> sortedMapOf(
comparator: Comparator<in K>,
vararg pairs: Pair<K, V>
): SortedMap<K, V>
The comparator you're passing in explicitly takes the key's type, i.e. the first value of each Pair is getting passed to the comparator, not the whole Pair.
That's why you're getting the error about Strings and Pairs - you told the Comparator that it's getting keys that are Pair<String, Double>s, but the Map you're passing in has String keys and Double values (since a Pair is treated as a key/value item).
You can't do what you're trying to do, basically! Is there another way to achieve whatever you're doing? A typical approach would be to just use a normal map, and make a sorted list when you need the output:
val myMap = mapOf("eat" to 11.0, "sleep" to 50.0)
val sorted = myMap.entries.sortedBy {it.value}

Generic transpose (or anything else really!) in Kotlin

Working on an Advent of Code puzzle I had found myself defining a function to transpose matrices of integers:
fun transpose(xs: Array<Array<Int>>): Array<Array<Int>> {
val cols = xs[0].size // 3
val rows = xs.size // 2
var ys = Array(cols) { Array(rows) { 0 } }
for (i in 0..rows - 1) {
for (j in 0..cols - 1)
ys[j][i] = xs[i][j]
}
return ys
}
Turns out that in the following puzzle I also needed to transpose a matrix, but it wasn't a matrix of Ints, so i tried to generalize. In Haskell I would have had something of type
transpose :: [[a]] -> [[a]]
and to replicate that in Kotlin I tried the following:
fun transpose(xs: Array<Array<Any>>): Array<Array<Any>> {
val cols = xs[0].size
val rows = xs.size
var ys = Array(cols) { Array(rows) { Any() } } // maybe this is the problem?
for (i in 0..rows - 1) {
for (j in 0..cols - 1)
ys[j][i] = xs[i][j]
}
return ys
}
This seems ok but it isn't. In fact, when I try calling it on the original matrix of integers I get Type mismatch: inferred type is Array<Array<Int>> but Array<Array<Any>> was expected.
The thing is, I don't really understand this error message: I thought Any was a supertype of anything else?
Googling around I thought I understood that I should use some sort of type constraint syntax (sorry, not sure it's called like that in Kotlin), thus changing the type to fun <T: Any> transpose(xs: Array<Array<T>>): Array<Array<T>>, but then at the return line I get Type mismatch: inferred type is Array<Array<Any>> but Array<Array<T>> was expected
So my question is, how do I write a transpose matrix that works on any 2-dimensional array?
As you pointed out yourself, the line Array(cols) { Array(rows) { Any() } } creates an Array<Array<Any>>, so if you use it in your generic function, you won't be able to return it when Array<Array<T>> is expected.
Instead, you should make use of this lambda to directly provide the correct value for the correct index (instead of initializing to arbitrary values and replacing all of them):
inline fun <reified T> transpose(xs: Array<Array<T>>): Array<Array<T>> {
val cols = xs[0].size
val rows = xs.size
return Array(cols) { j ->
Array(rows) { i ->
xs[i][j]
}
}
}
I don't really understand this error message: I thought Any was a supertype of anything else?
This is because arrays in Kotlin are invariant in their element type. If you don't know about generic variance, it's about describing how the hierarchy of a generic type compares to the hierarchy of their type arguments.
For example, assume you have a type Foo<T>. Now, the fact that Int is a subtype of Any doesn't necessarily imply that Foo<Int> is a subtype of Foo<Any>. You can look up the jargon, but essentially you have 3 possibilities here:
We say that Foo is covariant in its type argument T if Foo<Int> is a subtype of Foo<Any> (Foo types "vary the same way" as T)
We say that Foo is contravariant in its type argument T if Foo<Int> is a supertype of Foo<Any> (Foo types "vary the opposite way" compared to T)
We say that Foo is invariant in its type argument T if none of the above can be said
Arrays in Kotlin are invariant. Kotlin's read-only List, however, is covariant in the type of its elements. This is why it's ok to assign a List<Int> to a variable of type List<Any> in Kotlin.

How to skip specification of the generic type parameter in Kotlin?

This is the main body of my function
val client = ConnectionFactory.createClient() # <- Return lettice.io RedisClusterClient
val conn = client.connect()
val command = conn.sync()
var index: String? = null
index = readDataStructure(command, key)
This is my first try to define my readDataStructure function:
fun readDataStructure(command: RedisCommand, key: String): String {
...
kotlin complaints error: 3 type arguments expected for interface RedisCommand<K : Any!, V : Any!, T : Any!>
I want to be able to NOT specifying K, V and T because I am just writing a throwaway script.
Is there any Kotlin lang syntax and can allow me to just pass the command variable as is?
I suppose you are after:
fun readDataStructure(command: RedisCommand<*,*,*>, key: String): String {
?
From Kotlin docs https://kotlinlang.org/docs/tutorials/kotlin-for-py/generics.html:
If you don't have any idea (or don't care) what the generic type might be, you can use a star-projection:
fun printSize(items: List<*>) = println(items.size)
When using a generic type where you have star-projected one or more of its type parameters, you can:
Use any members that don't mention the star-projected type parameter(s) at all
Use any members that return the star-projected type parameter(s), but the return type will appear to be Any? (unless the type parameter is constrained, in which case you'll get the type mentioned in the constraint)
Not use any members that take a star-projected type as a parameter

Kotlin generic constraints - require param to be of same type as other

Let's say I have a set of two pojo's like so:
data class Test (
var id : Long? = null
)
data class TestOther (
var id : Long = 0,
var isCool : Boolean = false
}
and then I have an infix function like so:
infix fun <T : Any?> KProperty<T>.equal(rhs : KProperty<T>) = BinaryExpression<Boolean>(this, rhs, EQUALS)
then this works fine as I'd expect:
Test::id equal TestOther::id
but so does this, since T is all types that extend Any?:
Test::id equal TestOther::isCool
Is there anyway to specify generic constraints such that nullable and non nullable types can be compared, but objects of different types cannot without having to specify an overload for every possible concrete type?
It is not possible to do right now. You may follow the issue for more details
https://youtrack.jetbrains.com/issue/KT-13198
I see a workaround here (similar to the one from the issue). The idea is to wrap the KProperty<R> into a wrapper class without variance. As you see, the KProperty type has out R variance, which works against us in the example. You may follow the link for the details on the declaration-side variance in Kotlin
https://kotlinlang.org/docs/reference/generics.html#declaration-site-variance
The workaround works as strict as expected
class KWrapper<R>(val p : KProperty<R>)
infix fun <T : KWrapper<R>, R> T.equal(rhs : T) = false
val <T> KProperty<T>.wrap get() = KWrapper(this)
val a = Test::id.wrap equal TestOther::id.wrap //fails: Long vs Long?
val b = Test::id.wrap equal Test::id.wrap //works
val c = Test::id.wrap equal TestOther::isCool.wrap // fails Long vs Boolean
The downside is that you need to use .wrap extension property (or extension function) for the left and right parameters separately

Kotlin overload resolution ambiguity in Pair with reference to println

Using a reference to println as a Pair element fails when the reference is the first in the Pair.
>>> 0 to ::println
produces
(0, fun println(): kotlin.Unit)
but
>>> ::println to 0
gives
error: overload resolution ambiguity
Explicitly defining the pair using Pair() works fine in both cases.
What is the reason for this behaviour?
There are a couple of things going on here that you might find interesting.
Given that there's one version of println that takes no parameters, when you don't specify the type you expect ::println to be of, that's the version that's selected. [citation needed: I couldn't find any documentation/specification that says that this is the case, but that's what trying this out in Kotlin 1.2.71 shows]
The second piece is that the infix fun "to" is an extension method, so the type needs to be resolved before being able to invoke it.
For this reason, 0 to ::println gets automatically considered a Pair<Int, () -> Unit>.
To test this out, you can try the following:
fun foo(a: Int): Unit = Unit
fun foo(): Unit = Unit
val a = 0 to ::foo // the non-parameter version is selected
val b = ::foo to 0 // there's ambiguity. What extension method "to" to call?
val c: Pair<(Int) -> Unit, Int> = ::foo to 0 // No ambiguity, as I'm specifying the type
Now, if there are no overloads:
fun foo(a: Int): Unit = Unit
val a = 0 to ::foo // No ambiguity, there's only one to choose from
val b = ::foo to 0 // Same here, there's only one option
Finally, it gets interesting when you only have options WITH parameters:
fun foo(a: Int): Unit = Unit
fun foo(a: Int, b: Int): Unit = Unit
val a = 0 to ::foo // There's no non-parameter version to call, so there's ambiguity
val b = ::foo to 0 // there's ambiguity. What extension method "to" to call?