Usage of Kotlin typealias for Map.Entry in Map - kotlin

In Kotlin, it's possible to define typealias for classes, hence, also for Map<K,V>. Let's say, that I have the following:
typealias MyMap = Map<String, String>
But, what if I'd like to name the map entry as well, like this:
typealias MyEntry = Map.Entry<String, String>
typealias MyMap = Map<MyEntry> // error
However, Kotlin does not accept this, since Map<K,V> requires a type for the key and the value. Is such a thing as shown above possible?

No. This has nothing to do with typealias, rather how to declare generic types.
Map interface requires two type parameters and you must provide both otherwise you get the error, If you want to use a Map which is parameterized over its entry rather than Key, Value then you can define your own Map Type.
In the above case when you do Map<MyEntry>, you want the language to take the single type parameter (MyEntry) that you provide and extract its two components(String and String) and then use those two components as two different type parameters for the Map. Sorry you are asking too much.
MyEntry is a single type and it can only be used as such. Following is an example of that
typealias MyMap = Map<MyEntry, String>

The real answer is mightyWOZ's, but maybe this tip helps as well:
You can still use generics in typealiases to forward one of the arguments:
typealias StringMap<T> = Map<String, T>

Related

Is it possible to pass null type in place of generic type parameter?

I am going to use the following method from Spring Data Kotlin extensions:
inline fun <reified T : Any> MongoOperations.bulkOps(bulkMode: BulkMode, collectionName: String? = null): BulkOperations
The question is: can I somehow avoid specifying T assuming I do not want to provide entity class name (that's because I will explicitly specify collectionName, and in this case class type can be null). I would like to type something like:
val ops = mongoTemplate.bulkOps<null>(BulkOperations.BulkMode.UNORDERED, collectionName = "i_know_better")
Is there a type literal for null with which I can parameterize bulkOps?
I think the short answer is no.
You seem to confuse types with values. null is a value and not a type so it cannot be used as a type in generic methods.
In your specific example, even if you could use null, looking at the code what would you expect to happen?
#Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <reified T : Any> MongoOperations.bulkOps(bulkMode: BulkMode, collectionName: String? = null): BulkOperations =
if (collectionName != null) bulkOps(bulkMode, T::class.java, collectionName)
else bulkOps(bulkMode, T::class.java)
As you can see there's always T::class.java being called. What would be the result of null::class.java?
I'm unfamiliar with the Spring Data so I can't really provide an alternative, but I'd say you either need to search for another method or use an appropriate class here. The generic type is marked as T : Any so presumably it can be any non-nullable type. I wonder if Unit would work. Again, I'm not sure what this class is used for.
To answer the question in general, you can use Nothing? to represent the type that only contains the value null.
That being said, as #Fred already said, the method you're considering here explicitly states T : Any, meaning only non-nullable types are allowed. And it makes sense given that the function is accessing the class of T.

Map of generic interfaces in Kotlin

I stuck with some simple thing) Let's say I have following:
interface IMessagePayload // marker interface
data class IdPayload(
val id: Long
) : IMessagePayload
data class StringPayload(
val id: String,
) : IMessagePayload
Then I have a class:
data class Message<T : IMessagePayload>(
val id: String,
val payload: T,
)
Also I have some interface describing processor of this message:
interface IMessageProcessor<T : IMessagePayload> {
fun process(message: Message<T>)
}
And some implementation:
class ProcessorImpl : IMessageProcessor<IdPayload> {
override fun process(message: Message<IdPayload>) {
}
}
Now I wanna have a map of such processors. Lets use some enum type as a keys of this map:
enum class ActionType {
UPDATE,
DELETE,
ADD
}
private var map = mutableMapOf<ActionType, IMessageProcessor<IMessagePayload>>()
map[ActionType.ADD] = ProcessorImpl() // <-- error here
And that's where the problem occurs. I cannot put my ProcessorImpl into this map. The compiler says that there is an error: Type mismatch. Required: IMessageProcessor. Found: ProcessorImpl().
I could declare the map in the following way (using star projection):
private var map = mutableMapOf<ActionType, IMessageProcessor<*>>()
But in this case I cannot call processors's process method fetching it from the map by key first:
map[ActionType.ADD]?.process(Message("message-id", IdPayload(1))) // <-- error here
Compiler complains: Type mismatch. Required nothing. Found Message<IdPayload>
What am I doing wrong? Any help is appreciated.
This is about variance.
IMessageProcessor is defined as interface IMessageProcessor<T : IMessagePayload>; it has one type parameter, which must be IMessagePayload or a subtype.
But it is invariant in that type parameter; an IMessageProcessor< IdPayload> is not related to an IMessageProcessor<IMessagePayload>.  In particular, it's not a subtype.
And your map is defined with a value type IMessageProcessor<IMessagePayload>.  So its value cannot be an IMessageProcessor< IdPayload>, because that's neither the value type, nor a subtype.  Hence the compile error.
In this case, the simplest way to get it to compile is to change your map:
private var map = mutableMapOf<ActionType, IMessageProcessor<out IMessagePayload>>()
The only difference there is the out; that tells the compiler that the value IMessageProcessor is covariant in its type parameter.  (It may help to think of out as meaning ‘…or any subtype’.  Similarly, you could make it contravariant by using in, which you might think of as ‘…or any supertype’.)
This lets you store in the map an IMessageProcessor for any subtype of IMessagePayload.
However, if you do that, you'll find that you can't use any value you pull out of your map — because it can't tell which messages the processor can handle, i.e. which subtype of IMessagePayload it works for!  (The compiler expresses this as expecting a type parameter of Nothing.)
In general, it's often better to specify variance on the interface or superclass itself (declaration-site variance) rather than the use-site variance shown above.  But I can't see a good way to do that here, because you have multiple generic classes, and they interact in a complicated way…)
Think for a moment what IMessageProcessor's type parameter means: it's the type of message that the processor can consume. So an IMessageProcessor<A> can handle messages of type Message<A>.
Now, a subtype must be able to do everything its supertype can do (and usually more) — otherwise you can't drop that subtype anywhere that's expecting to use the supertype.  (That has the grand name of the Liskov substitution principle — but it's really just common sense.)
So an IMessageProcessor<B> is a subtype of IMessageProcessor<A> only if it can handle at least all the messages that an IMessageProcessor<A> can.  This means it must accept all messages of type Message<A>.
But Message is invariant in its type parameter: a Message<B> is not directly related to a Message<A>.  So you can't write a processor that handles them both.
The most natural solution I can find is to specify variance on both Message and IMessageProcessor:
data class Message<out T : IMessagePayload>( /*…*/ )
interface IMessageProcessor<in T : IMessagePayload> { /*…*/ }
And then use a wildcard in your map to make it explicit that you don't know anything about the type parameters of its values:
private var map = mutableMapOf<ActionType, IMessageProcessor<*>>()
That lets you safely store a ProcessorImpl() in the map.
But you still have to use an (unchecked) cast on the values you pull out of the map before you can use them:
(map[ActionType.ADD] as IMessageProcessor<IdPayload>)
.process(Message("4", IdPayload(4L)))
I don't think there's any easy way around that, because the problem is inherent in having values which are processors that can handle only some (unknown) types of message.
I'm afraid the best thing would be to have a rethink about what these classes mean and how they should interact, and redesign accordingly.

How can I define Map of String or Int in Kotlin?

Is there way to declare a Map that would accept both String and Int in Kotlin? Like:
var map: Map<String, String | Int>
There is no union type in Kotlin, so you can't directly express this.
A few options:
use TreeMap<String, Any>, which is the simplest of all, but it allows more types than just Int and String, and requires additional conversion boilerplate on the consumer side when accessing values.
use a custom sealed class to encapsulate your "Int or String" type, and use it in a regular TreeMap<String, IntOrString>. This will have some runtime overhead for wrapping/unwrapping the int/string objects for each insertion/access. Also, this puts the burden of (un)wrapping on the consumer side. That being said, maybe your domain can be better modeled with clearer names than Int or String, so it might actually improve the consumer's experience to define such types.
use a custom map type, with independent accessors for ints and strings, but backed by a single TreeMap<String, Any> behind the scenes. This has the advantage of encapsulating the logic and exposing a neat API for the consumers. If you extend TreeMap you will save some overhead as opposed to composition, but it shouldn't matter much.
I am wondering about the actual use case at hand, though. Usually the need for union types stems from using plain generic primitive types to represent meaningful domain data that could maybe better be expressed using more specific custom domain types.
Option 2 would therefore usually be my choice, but I would use a sealed class that actually represents my domain data instead of the generic "string or int".
You can use Any here, see this example:
import java.util.TreeMap
fun main() {
var map = TreeMap<String, Any>()
map.put("One", 1)
map.put("Two", "two")
println(map)
}
which outputs
{One=1, Two=two}
Please note:
This will allow more types than just the desired String and Int, because the Kotlin Docs state (...). Every Kotlin class has Any as a superclass..

Kotlin non nullable map allows remove null

Why this code can be compiled and executed without erros?
val map = HashMap<Int, Long>()
val key :Int? = null
map.remove(key)
In MutableMap remove declared as accepting only non nullable key, so it shouldn't even compile. Is it a Kotlin type inference bug or am I missing something?
public fun remove(key: K): V?
Your code is perfectly fine as remove() allows nullable arguments - your map contents definition got nothing to it. When remove() is invoked, it would try to find matching requested key in the map and as it's not there (it's completely irrelevant why it's not there - it's valid case for key to be not present) nothing will happen. Where compiler will complain is on any attempt to put such key into your map. Then map definition kicks in and since it's known that nullable keys not allowed, such code won't even compile as this is clearly buggy code.
In this case, map.remove(key) doesn't not calls
public fun remove(key: K): V?
It calls an extension remove function:
public inline fun <#OnlyInputTypes K, V> MutableMap<out K, V>.remove(key: K): V? =
#Suppress("UNCHECKED_CAST") (this as MutableMap<K, V>).remove(key)
This function documentation says that it allows to overcome type-safety restriction of remove that requires to pass a key of type K.
It allows overcoming type-safety restriction because the key of the entry you are removing does not have to be the same type as the object that you pass into remove(key); the specification of the method only requires that they be equal. This follows from how the equals() method takes in an Any as a parameter, not just the same type as the object.
Although it may be commonly true that many classes have equals() defined so that its objects can only be equal to objects of its own class, there are many places where this is not the case. For example, the specification for List.equals() says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List. So, for example, according to the specification of the method, it is possible to have a MutableMap<ArrayList<Something>, Something> and call remove(key) with a LinkedList as an argument, and it should retrieve the key which is a list with the same contents. This would not be possible if this extension remove(key) didn't exist.[1]
Kotlin could warn or refuse to compile (would be good), but it doesn't (for now).
The reason for it being not as bad as it looks from a first glance is that you cannot put an Int? into a MutableMap<Int, Long> because
val map = HashMap<Int, Long>()
val key :Int? = null
map.put(key, 1) // <--- WON'T COMPILE [Type mismatch: inferred type was Int? but Int was expected]
map.remove(key)
Nevertheless, I think you are right by wondering about that method being compiled.
Eventually asking this question helped to find another question with explanation. In short, what actually happens is call of the extension function which have it's own type inference.

Kotlin's reflection : Unknown type parameter

I am running some experiments on Kotlin's reflection.
I am trying to get a reflection object of a generic class with its argument.
In Java, that would be a ParameterizedType.
The way to get such a thing using Java's reflection API is a bit convoluted: create an anonymous subclass of a generic class, then get its super-type first parameter.
Here's an example:
#Suppress("unused") #PublishedApi
internal abstract class TypeReference<T> {}
inline fun <reified T> jGeneric() =
((object : TypeReference<T>() {}).javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
When I println(jGeneric<List<String?>>()), it prints java.util.List<? extends java.lang.String>, which is logical considering that Kotlin's List uses declaration-site out variance and that Java types have no notion of nullability.
Now, I would like to achieve the same kind of result, but with the Kotlin reflection API (that would, of course, contain nullability information).
Of course, List<String>::class cannot work since it yields a KClass. and I am looking for a KType.
However, when I try this:
inline fun <reified T> kGeneric() =
(object : TypeReference<T>() {})::class.supertypes[0].arguments[0].type
When I println(kGeneric<List<String?>>()), it prints [ERROR : Unknown type parameter 0], which is quite... well, anticlimactic ;)
How can I get, in Kotlin, a KType reflecting List<String> ?
To create a KType instance in Kotlin 1.1, you have two options:
To create a simple non-nullable type out of a KClass, where the class is either not generic or you can substitute all its type parameters with star projections (*), use the starProjectedType property. For example, the following creates a KType representing a non-nullable type String:
val nonNullStringType = String::class.starProjectedType
Or, the following creates a KType representing a non-nullable type List<*>:
val nonNullListOfSmth = List::class.starProjectedType
For more complex cases, use the createType function. It takes the class, type arguments and whether or not the type should be nullable. Type arguments are a list of KTypeProjection which is simply a type + variance (in/out/none). For example, the following code creates a KType instance representing List<String>:
val nonNullStringType = String::class.starProjectedType
val projection = KTypeProjection.invariant(nonNullStringType)
val listOfStrings = listClass.createType(listOf(projection))
Or, the following creates the type List<String>?:
val listOfStrings = listClass.createType(listOf(projection), nullable = true)
Both starProjectedType and createType are defined in package kotlin.reflect.full.
We're planning to introduce the possibility of getting a KType instance simply from a reified type parameter of an inline function which would help in some cases where the needed type is known statically, however currently it's not entirely clear if that's possible without major overhead. So, until that's implemented, please use the declarations explained above.