Apologies if OOT in this channel or if there is an...
# compiler
a
Apologies if OOT in this channel or if there is an existing issue already that i couldn't find , but i would like to understand if the following behaviour is expected. Here is a MWE to illustrate my problem.
Copy code
fun minimalExample(
  ids : List<Long>,
  details : Map<String, Boolean>
) : List<Boolean?> =
  ids.map { id -> details.get(id) }
I'm mapping over a list of
Longs
and for each
Long
i try to access the corresponding value from a key-value map; the map however here is of type
Map<String, Whatever>
, not
Map<Long, Whatever>
I've done that on purpose so that when writing
details.get(id)
the types don't match I would have expected the compiler to throw a compilation error, something like
type mismatch: inferred type is Long but String was expected
Instead i can compile the code and run it (obviously at runtime there is jvm type erasure on generic and details.get(id) will always return null in this case). there is a warning
Copy code
warning: type inference failed. The value of the type parameter K should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly.
the problem with this warning is two fold: 1. it's error prone. I can handle all warnings as errors, or set a linter, but i would have expected the inference to work just fine in this example 2. there seems to be no obvious way to me to provide the extra information that the compiler would need in order for the type inference to work. is this behaviour expected or is an issue?
m
Regarding #2:
Copy code
fun minimalExample(
	ids: List<Long>,
	details: Map<String, Boolean>,
): List<Boolean?> =
	ids.map { id -> details.get<Any,Boolean>(id) }
a
Any
is not what's expected here, the map is
<String,Boolean>
and the get should be
<String, Boolean>
. Writing explicitly
Any
is equivalent to not write the types specifically, because that's what the compiler is going to infer anyway. #2 was about restoring type safety in a meaningful way in this example.
m
I think it is as it’s supposed to be. It’s likely a compromise between type safety on Kotlin’s side and compatibility with Java. In Java the key for
get()
is merely
Object
, i.e.
Any
.
a
Interoperability is not the problem here: it would fail normally if i just do it outside the
map{ ..}
over list
m
This is not related to the
map
operator.
Copy code
fun minimalExample(
	ids: List<Long>,
	details: Map<String, Boolean>,
): List<Boolean?> =
	listOf(details.get(2))
Also works
a
but
details.get(2)
no
m
Works just fine:
Copy code
fun minimalExample(details: Map<String, Boolean>): Boolean? =
	details.get(2)
a
true, your are right in saying that it's failing even in the simplest case
Copy code
val aMap : Map<String, Boolean> = mapOf()·¬
¬
val x : Boolean? = aMap.get(true)¬
This also compiles (with the type inference warning).. Maybe i'm too used to scala/haskell type safety, but as a person that relies a lot on the compiler to spot these issues i was really surprised to discover that access to the elements of a map is actually not so type safe. It might be linked to interoperability with java collections then, but in that case the fact that Map<T,V> is parametrised over two types is kinda misleading
m
Yeah, maybe one day they can remove the extension functions that allow out-projected keys. Then it would be fully type-safe. Once Java is not that important anymore - if that’s the reason for these overloads in the first place.
It also seems to be related to
Map<*,*>
(cannot call
.get()
there without that workaround) or things like
Map<out Something, *>
.
a
😅 Completely randomly and independently I have come to the same question: https://kotlinlang.slack.com/archives/C0922A726/p1608132871128300 Would also like to see some comments from the team. Such things at least should be documented somewhere.
a
@alllex @Marc Knaup thanks very much for the conversation and for the reference material/links, much appreciated