I'm building a Kotlin microservice that processes events of various types and publishes them out to external partner systems. I have all of the event classes implementing a common interface called GenericPartnerEvent:
interface GenericPartnerEvent
data class EventTypeOne(
val key: String,
val payload: String
) : GenericPartnerEvent
I then have a series of event handler classes, one per external partner, that are defined like this:
interface PartnerEventHandler<in T : GenericPartnerEvent> {
fun handleEvent(event: T)
}
class PartnerOneEventTypeOneHandler : PartnerEventHandler<EventTypeOne> {
...
}
I want a generic way to fetch the event handler class of the correct type given a partner and event type; something like:
fun getEventHandler(partner: String, eventType: String): PartnerEventHandler<GenericPartnerEvent> {
if (...something...) return PartnerOneEventTypeOneHandler()
}
But what I inevitably get is something like:
Type mismatch.
Required: PartnerEventHandler<GenericPartnerEvent>
Found: PartnerOneEventTypeOneHandler
Is there a way to do this?
No, since your PartnerEventHandler type is contravariant (in), a subtype's type cannot be upcast. A PartnerEventHandler<EventTypeOne> cannot accept any subtype of GenericPartnerEvent as an input, so a PartnerEventHandler<EventTypeOne> is not a subtype of PartnerEventHandler<GenericPartnerEvent>.
Since you retrieve handlers using a String representation of the type, I don't see how generics buy you anything here anyway. The handle function might as well take an input type of Any and reject incorrect types with an exception or by returning null or something.
FWIW, this approach seems to pass muster with the compiler:
inline fun <reified T : GenericPartnerEvent> getHandlerForPartner(partner: String): PartnerEventHandler<T>? {
return when (partner) {
"partner-one" -> when (T::class) {
EventTypeOne::class -> PartnerOneEventTypeOneHandler() as PartnerEventHandler<T>
EventTypeTwo::class -> PartnerOneEventTypeTwoHandler() as PartnerEventHandler<T>
else -> null
}
"partner-two" -> when (T::class) {
EventTypeOne::class -> PartnerTwoEventTypeOneHandler() as PartnerEventHandler<T>
EventTypeTwo::class -> PartnerTwoEventTypeTwoHandler() as PartnerEventHandler<T>
else -> null
}
else -> null
}
}
It does give me a warning: Unchecked cast: PartnerOneEventTypeOneHandler to PartnerEventHandler<T>. So I guess we'll see what happens at runtime.
Related
I have something like this :
import kotlin.reflect.KClass
class Quantity<T> {
/* ... */
}
class Field<T : Any> {
val type: KClass<T> get() = TODO("This is initialized, don't worry about implentation details, just know that fields know their type.")
fun initValue(value: T) {
/* Do something very useful */
}
/* Other methods */
class Template<T : Any> {
fun initFieldWithValue(value: T): Field<T> {
return Field<T>().apply {
this.initValue(value)
}
}
}
}
class ComponentClass(
val fieldsTemplates: Map<String, Field.Template<*>>
) {
inner class Instance(field: Map<String, Field<*>>)
fun new(fieldValues: Map<String, Quantity<*>>): Instance {
val fields = mutableMapOf<String, Field<*>>()
for ((fieldName, template) in fieldsTemplates) {
fields[fieldName] = fieldsTemplates
.getValue(fieldName)
.initFieldWithValue(fieldValues.getValue(fieldName) /* Here a type error */)
}
return Instance(fields)
}
}
As you might guess, this is intended to work as a 'runtime way' of creating classes that own fields (Field<T> class), each one possessing a typed value (represented by a Quantity<T>).
The problem is that this code won't compile due to the fact that the quantity retrieved from fieldValues when creating the different fields of the future Instance in the new method isn't guaranteed to be of the required type for the field it is stuffed into.
The problem is that I would need a check since filling a Field<Quantity<String>> with a Quantity<Int> is obviously not a good idea, but because of the type erasure I cannot ensure that the quantities passed in are of the good type.
Any idea ? One more thought : Fields know what their type is thanks to their type attribute, but unfortunately I can't do the same for the Quantity class...
Your initFieldWithValue function is enforcing the type of the parameter to match the type known by the Template/Field. But inside your new function, your Template is a Template<*> since you retrieve it from a collection where the values are of this type.
The point of generics is to enforce compile time checks so casting can be done safely and automatically under the hood. This is only useful when your type is known at compile time. In this case, the type is not known at compile time, so the generics are preventing your code from compiling. This is what generics are supposed to do: prevent code from compiling if the compiler cannot check that they types match.
If you want this code to compile, you should change initFieldWithValue so it doesn't enforce generics. You can instead manually check the type and throw an error or exit early if it's incorrect. It will be up to your code elsewhere to ensure you aren't mixing and matching types.
Here's an example of a version that would work. The type check it does requires the Kotlin reflection library. If you're targeting JVM only, you can use the Java Class.isAssignableFrom method instead to do this check.
class Template<T : Any> {
val type: KClass<T> get() = TODO()
/**
* #throws IllegalStateException if [value] is not of the same type
* as this Template's [type].
*/
fun initFieldWithValue(value: Any): Field<T> {
if (!value::class.isSubclassOf(type)) {
error("Invalid value type for Field type of $type")
}
return Field<T>().apply {
#Suppress("UNCHECKED_CAST") // we manually checked it above
initValue(value as T)
}
}
}
I'm trying to build a resolver that given some domain context return back an implementation of a generic interface. The code is the following (domain abstracted):
interface Interface<T>
class StringImplementation: Interface<String>
class BooleanImplementation: Interface<Boolean>
class Resolver {
fun <T : Any> resolve(implementation: String): Interface<T> {
return when (implementation) {
"string" -> StringImplementation()
"boolean" -> BooleanImplementation()
else -> throw IllegalArgumentException()
}
}
}
This snippet looks good to me but the compiler is complaining because Type missmatch: Required: Interface<T> Found: StringImplementation at line 11 and Type missmatch: Required: Interface<T> Found: BooleanImplementation at line 12.
Why is that a problem? I though setting <T : Any> in the method contract would allow to return an implementation of any type. The constraing here is that the return type of the method resolve must be Interface<T>, replacing it with Interface<*> would make the compiler shut up but is not what we need.
TL;DR
A function can have exactly 1 return type, but your function has 2 different return types.
Only this would work:
interface Interface<T>
class StringImplementation: Interface<String>
class BooleanImplementation: Interface<Boolean>
class Resolver {
fun resolve(implementation: String): Interface<*> { // <-- star
return when (implementation) {
"string" -> StringImplementation()
"boolean" -> BooleanImplementation()
else -> throw IllegalArgumentException()
}
}
}
Explanation
Looking from the function definition perspective, it has to have an explicit, clear return type. Interface<T> says it should be something extending Interface and the explicit type T which concrete implementation can be known by the start of execution of the function.
There is no way in your code to know what T will be when you call resolve. How else would you imagine the function to know what it will return back?!
Shortened: A function can have exactly 1 return type, but your function has 2 different return types (Interface<String> / Interface<Boolean>).
Continue to read here if you want to dig down into generics and get a more technical description.
The compiler cannot know if T matches the implementation variable. Even if implementation is string, T could be of another type then String. So you either can erase the generic type like #Neo mentioned or you need to cast the return type.
interface Interface<T>
class StringImplementation: Interface<String>
class BooleanImplementation: Interface<Boolean>
class Resolver {
inline fun <reified T : Any> resolve(): Interface<T> {
return when (T::class) {
String::class -> StringImplementation() as Interface<T>
Boolean::class -> BooleanImplementation() as Interface<T>
else -> throw IllegalArgumentException()
}
}
}
To have more type safety, you can use a reified parameter and use this to resolve the type. (Note that the cast is still necessary)
I though setting <T : Any> in the method contract would allow to return an implementation of any type.
No, it means it has to return an implementation of any type the caller asks for. E.g., in Animesh Sahu's example, resolve<Boolean>("string") must return an Interface<Boolean>, but your implementation of resolve would return a StringImplementation. Of course, it could also be resolve<File>("string") etc.
allow to return an implementation of any type
which the called method chooses is exactly Interface<*>.
Taking my first steps in Kotlin, I'm struggling to find the correct signature for a function that receives an instance of a known class along with the desired output class and then looks in a map of converter lambdas whether the conversion can be done.
Here's an example for Long:
private fun <T> castLong(value: Long, clazz: Class<out T>): T {
// map lookup removed for simplicity
return when (clazz) {
String::class.java -> { value.toString() }
else -> { throw IllegalArgumentException("Unsupported Cast") }
}
}
Where T is the class of the desired return value - let's say String. One should be able to call castLong(aLongValue, String::class.java) and receive an instance of String.
But the compiler says:
Type mismatch: inferred type is String but T was expected
This seems like it should be possible as it is quite straightforward so far but even playing around with reified and other constructs didn't yield any better results.
It happens because it can't smart cast String to T, you have to manually cast it.
Furthermore, since you said you are taking your first steps in Kotlin, I leave here two other "advices" not strictly related to your question:
you can get the class of T making it reified
the brackets of a case using when aren't necessary if the case is one line
private inline fun <reified T> castLong(value: Long): T {
// map lookup removed for simplicity
return when (T::class.java) {
String::class.java -> value.toString()
else -> throw IllegalArgumentException("Unsupported Cast")
} as T
}
I need to be able to tell the generic type of kotlin collection at runtime. How can I do it?
val list1 = listOf("my", "list")
val list2 = listOf(1, 2, 3)
val list3 = listOf<Double>()
/* ... */
when(list.genericType()) {
is String -> handleString(list)
is Int -> handleInt(list)
is Double -> handleDouble(list)
}
Kotlin generics share Java's characteristic of being erased at compile time, so, at run time, those lists no longer carry the necessary information to do what you're asking. The exception to this is if you write an inline function, using reified types. For example this would work:
inline fun <reified T> handleList(l: List<T>) {
when (T::class) {
Int::class -> handleInt(l)
Double::class -> handleDouble(l)
String::class -> handleString(l)
}
}
fun main() {
handleList(mutableListOf(1,2,3))
}
Inline functions get expanded at every call site, though, and mess with your stack traces, so you should use them sparingly.
Depending on what you're trying to achieve, though, there's some alternatives. You can achieve something similar at the element level with sealed classes:
sealed class ElementType {
class DoubleElement(val x: Double) : ElementType()
class StringElement(val s: String) : ElementType()
class IntElement(val i: Int) : ElementType()
}
fun handleList(l: List<ElementType>) {
l.forEach {
when (it) {
is ElementType.DoubleElement -> handleDouble(it.x)
is ElementType.StringElement -> handleString(it.s)
is ElementType.IntElement -> handleInt(it.i)
}
}
}
You can use inline functions with reified type parameters to do that:
inline fun <reified T : Any> classOfList(list: List<T>) = T::class
(runnable demo, including how to check the type in a when statement)
This solution is limited to the cases where the actual type argument for T is known at compile time, because inline functions are transformed at compile time, and the compiler substitutes their reified type parameters with the real type at each call site.
On JVM, the type arguments of generic classes are erased at runtime, and there is basically no way to retrieve them from an arbitrary List<T> (e.g. a list passed into a non-inline function as List<T> -- T is not known at compile-time for each call and is erased at runtime)
If you need more control over the reified type parameter inside the function, you might find this Q&A useful.
Does Kotlin have anything like discriminated unions (sum types)? What would be the idiomatic Kotlin translation of this (F#):
type OrderMessage =
| New of Id: int * Quantity: int
| Cancel of Id: int
let handleMessage msg =
match msg with
| New(id, qty) -> handleNew id qty
| Cancel(id) -> handleCxl id
Kotlin's sealed class approach to that problem is extremely similar to the Scala sealed class and sealed trait.
Example (taken from the linked Kotlin article):
sealed class Expr {
class Const(val number: Double) : Expr()
class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
The common way of implementing this kind of abstraction in an OO-language (e.g. Kotlin or Scala) would be to through inheritance:
open class OrderMessage private () { // private constructor to prevent creating more subclasses outside
class New(val id: Int, val quantity: Int) : OrderMessage()
class Cancel(val id: Int) : OrderMessage()
}
You can push the common part to the superclass, if you like:
open class OrderMessage private (val id: Int) { // private constructor to prevent creating more subclasses outside
class New(id: Int, val quantity: Int) : OrderMessage(id)
class Cancel(id: Int) : OrderMessage(id)
}
The type checker doesn't know that such a hierarchy is closed, so when you do a case-like match (when-expression) on it, it will complain that it is not exhaustive, but this will be fixed soon.
Update: while Kotlin does not support pattern matching, you can use when-expressions as smart casts to get almost the same behavior:
when (message) {
is New -> println("new $id: $quantity")
is Cancel -> println("cancel $id")
}
See more about smart casts here.
The sealed class in Kotlin has been designed to be able to represent sum types, as it happens with the sealed trait in Scala.
Example:
sealed class OrderStatus {
object Approved: OrderStatus()
class Rejected(val reason: String): OrderStatus()
}
The key benefit of using sealed classes comes into play when you use them in a when expression for the match.
If it's possible to verify that the statement covers all cases, you don't need to add an else clause to the statement.
private fun getOrderNotification(orderStatus:OrderStatus): String{
return when(orderStatus) {
is OrderStatus.Approved -> "The order has been approved"
is OrderStatus.Rejected -> "The order has been rejected. Reason:" + orderStatus.reason
}
}
There are several things to keep in mind:
In Kotlin when performing smartcast, which means that in this example it is not necessary to perform the conversion from OrderStatus to OrderStatus.Rejected to access the reason property.
If we had not defined what to do for the rejected case, the compilation would fail and in the IDE a warning like this appears:
'when' expression must be exhaustive, add necessary 'is Rejected' branch or 'else' branch instead.
when it can be used as an expression or as a statement. If it is used as an expression, the value of the satisfied branch becomes the value of the general expression. If used as a statement, the values of the individual branches are ignored. This means that the compilation error in case of missing a branch only occurs when it is used as an expression, using the result.
This is a link to my blog (spanish), where I have a more complete article about ADT with kotlin examples: http://xurxodev.com/tipos-de-datos-algebraicos/
One would be doing something like this:
sealed class Either<out A, out B>
class L<A>(val value: A) : Either<A, Nothing>()
class R<B>(val value: B) : Either<Nothing, B>()
fun main() {
val x = if (condition()) {
L(0)
} else {
R("")
}
use(x)
}
fun use(x: Either<Int, String>) = when (x) {
is L -> println("It's a number: ${x.value}")
is R -> println("It's a string: ${x.value}")
}