I'm new to Kotlin and what I'm trying to achieve is very simple in dynamically typed languages like Python and JavaScript, but no so easy in Kotlin. I have a set of message handler functions accepting message objects. Each message class implements Message interface. I want to map each message handler function to a String key:
interface Message
data class TestMessage1(val body: String): Message
data class TestMessage2(val body: String): Message
fun testMessage1Handler(msg: TestMessage1) { println(msg.body) }
fun testMessage2Handler(msg: TestMessage2) { println(msg.body) }
val functions = mapOf<String, (Message)->Unit> (
"testMessage1" to ::testMessage1Handler,
"testMessage2" to ::testMessage2Handler
)
This code gives me two errros:
error: type inference failed.
Expected type mismatch: inferred type is
Pair<String, KFunction1<#ParameterName Line_1.TestMessage1, Unit>>
but
Pair<String, (Line_1.Message) -> Unit> was expected
error: type inference failed.
Expected type mismatch: inferred type is
Pair<String, KFunction1<#ParameterName Line_1.TestMessage2, Unit>>
but
Pair<String, (Line_1.Message) -> Unit> was expected
Why can't I use interface Message as function type parameter? Since both TestMessage1 and TestMessage2 implement this interface it seems correct to me. How would you implement something like this?
There is a related question How to save a function reference as the value in a Map type, and invoke it with a parameter later on in Kotlin? but I don't want to change message handler parameter msg type to Any
Let's look at in another way.
What if instead of trying and failing to specify the type, we give Kotlin chance to infer it, by specifying incorrect type:
val functions: String = mapOf(
"testMessage1" to ::testMessage1Handler,
"testMessage2" to ::testMessage2Handler
)
This produces:
inferred type is Map<String, KFunction1<*, Unit>> but String was expected.
Now if you put that signature, it will actually compile:
val functions = mapOf<String, KFunction1<*, Unit>>(
"testMessage1" to ::testMessage1Handler,
"testMessage2" to ::testMessage2Handler
)
This is similar to:
val functions: Map<String, KFunction1<*, Unit>> = mapOf(
"testMessage1" to ::testMessage1Handler,
"testMessage2" to ::testMessage2Handler
)
When trying to invoke this, though, you get a hint why this is not correct:
functions["testMessage1"]?.invoke(TestMessage1("a"))
Gets you
Type mismatch.
Required: Nothing
Found: TestMessage1
Notice that Kotlin infers that type of your input is Nothing
To understand why, let's look at only one function:
fun testMessage1Handler(msg: TestMessage1)
And ask ourselves: what other type besides TestMessage1 this function may receive? No other type. It cannot receive TestMessage2. It cannot receive Message, since Message is not necessarily TestMessage1.
For second function, we'll get the same answer. So, the common type of both is Nothing. Which makes this abstraction not very useful to begin with.
The following code compiles but renders the question kind of moot.
The type parameters match now. Maybe generics could be applied as well, but I don’t know how.
interface Message {
val body: String
}
data class TestMessage1(override val body: String): Message
data class TestMessage2(override val body: String): Message
fun messageHandler(msg: Message) { println(msg.body) }
val functions = mapOf<String, (Message)->Unit> (
"testMessage1" to ::messageHandler,
"testMessage2" to ::messageHandler
)
Related
I work with a lot's of generated kotlin data classes (openapi-generated) with val's and two common fields available and processed. So i can assign values only on construction, like:
data class StringRepresentation {
val value: String,
val available: Boolean,
val processed: Boolean,
}
data class DoubleRepresentation {
val value: Double,
val available: Boolean,
val processed: Boolean,
}
And i have to init lot's of them with common robust code, like:
val currentRepresentation = StringRepresentation("Something", true, false)
Can any pattern or Kotlin language support be used to remove robost object initialization?
It could be wonderful to use some kind of generic template method, something like this:
private inline fun <reified T: StringRepresentation> buildRepresentation(
value: Any,
available: Boolean,
processed: Boolean
): T {
return when(T) {
is StringRepresentation -> StringRepresentation(value.toString(), available, processed)
else -> ...
}
}
, but my types and properties are final and also syntax doesn't allow to set multiple generic boundaries. I can't figure it out the right approach for this. I guess in that case I need to write a builder, but this seems to be java way.
Is there any kotlin way to do this?
I do not think that it is possible with your setup to write a builder that would actually be useful. Your *Representation data types stand in no explicit type relation to their respective type parameter (e.g. a StringRepresentation is not Something<String>), so what should be the return type of a generic builder function? It could only be Any and you would need to cast every result to its expected type in order to use it.
What you can do is to define a generic data class:
data class Representation<T>(
val value: T,
val available: Boolean,
val processed: Boolean,
)
I know, you cannot use that class as super class of your specific data classes, but you can write extension functions that convert the generic representation for one value type into its corresponding specific representation:
fun Representation<String>.typed() = StringRepresentation(value, available, processed)
fun Representation<Double>.typed() = DoubleRepresentation(value, available, processed)
Then you can use the same code to create a data object of any type:
val stringRepresentation: StringRepresentation = Representation("x", false, true).typed()
val doubleRepresentation: DoubleRepresentation = Representation(1.0, false, true).typed()
But please note that this is still not a generic solution since whatever you put into the constructor Representation has to be typed explicitly as a String or Double, respectively.
Let's say you define a generic function for all undefined value types:
fun <T: Any> Representation<T>.typed(): Any = error("no typed representation for value type ${value::class}")
The specific cases above will still work, and you could additionally write something like this:
val x : Any = 2.0
val someRep: Any = Representation(x, true, false).typed()
This is syntactically correct and will compile, but it will not work as desired, because what you get is an IllegalArgumentException ("no typed representation for value type class kotlin.Double"), because x is not typed as Double, but as Any.
I have a generic function to fetch/get any list out of the SharedPreferences. However, when I wanted to test, that it does not work, when I saved a list of say, Messages and ask for a list of say, Ints, it still worked. It just ignored the type I precised and returned a List of JsonObjects. When I debugged the whole code, I found, that apparently the function does not care about the inferred class type. I´ll first put here the code, so I can explain the problem:
fun <T> getListFromPreferences(preferences : SharedPreferences, key : String)
: MutableList<T> {
val listAsString = preferences.getString(key, "")
val type: Type = object : TypeToken<List<T>>() {}.type
val gson = SMSApi.gson
return gson.fromJson<ArrayList<T>>(listAsString, type)
?: ArrayList()
}
So, what I would expect, was, that when I call the function like this:
PreferenceHelper.getListFromPreferences<Message>(preferences, TEST_KEY)
the "type" variable in the above code should return List. However the result the debugger shows me is: java.util.List<? extends T>
I have absolute no idea, why the inferring does not work, but I´d really like it to work to ensure, what I am requesting is actually what I get, for obvious reasions.
Does anybody know a reason and a solution for this weird behaviour?
Due to type erasure, actual type information about T is lost, so basically this method returns List<Any?> (even if you pass Int as T).
To preserve the actual type, you need to declare this method with reified parameter:
inline fun <reified T> getListFromPreferences(preferences : SharedPreferences, key : String)
: MutableList<T> {
//...
}
If you are familiar with unity3d, I'm trying to implement a similar pattern for unity components:
AddComponent<T>();
GetComponent<T>();
So I made this snippet in kotlin:
val map = mutableMapOf<Class<Any>,IComponent>()
fun <T : IComponent> addComponent(component : T){
map.put(component.javaClass,component)
}
fun <T : IComponent> getComponent(klazz : Class<T>): T {
return map.get(klazz)
}
First of all, I have to pass a class to the getComponent method, I can't infer the type from T like C# and I was wondering if there is a way to do this.
And most importantly, why is the method giving me a compile error saying the I'm returning IComponent where T is required, although I did say that T IS an IComponent?
I have to cast to T which is unsafe, this works perfectly fine in C# universe but I'm new to kotlin and I'm wondering if that's possible.
First of all, I have to pass a class to the getComponent method, I can't infer the type from T like C# and I was wondering if there is a way to do this
To be able do something like this the type have to be reified.
In your case it would be something like this:
inline fun <reified T : IComponent> getComponent(): T {
val klazz = T::class.java
// something-something that returns T
}
and most importantly, why is the method giving me a compile error saying the I'm returning IComponent where T is required, although I did say that T IS an IComponent
You said that the type T is IComponent, but not that IComponent is T. And the map contains IComponent as values. Some of them can be T, but there're no guarantee for compiler that they are. So compiler falls with error, and says it isn't sure you will get something of type T out of the map.
So you need to force cast result to the type:
return map.get(klazz) as T // in your case you will have to cast klazz to Class<Any>, btw
To make it castles you should define map as
val map = mutableMapOf<Class<*>,IComponent>()
instead of
val map = mutableMapOf<Class<Any>,IComponent>()
Also, it would be better to use optional type T? for getComponent, in pair with conditional cast as?.
I've got the following data class
data class MyResponse<T>(val header: String,
val body: T)
I want to write a generic Kotlin function that can deserialise json to various MyResponse<SimpleBody> or MyResponse<ComplexBody>
class JsonSerialiser {
val mapper = jacksonObjectMapper()
fun <T> fromJson(message: String, classz: Class<T>): T {
return mapper.readValue(message, classz)
}
}
val response: MyResponse<SimpleBody> = jsonSerialiser.fromJson("""{"header":"myheader", "body":{"simpleBody":{"name": "A"}}}""", MyResponse::class.java)
data class SimpleBody(val name: String)
It failed to compile with this error message
Kotlin: Type inference failed. Expected type mismatch: inferred type is MyResponse<*> but MyResponse<SimpleBody> was expected
Anyone knows how I can get this to work? I could use MyResponse<*> and then cast it, but I don't feel that's the right way.
Thanks & regards
Tin
You don't need this, just use com.fasterxml.jackson.module.kotlin.readValue:
val response: MyResponse<SimpleBody> = jacksonObjectMapper().readValue("""{"header":"myheader", "body":{"name": "A"}}""")
Please note that I changed the json a bit so that Jackson can parse it without further modifications
val specials:Map<String, (Any)->Unit> = mapOf(
"callMe1" to {asParam1()},
"callMe2" to {asParam2()}
)
fun asParam1(num:Int) {
println(num)
}
fun asParam2(text:String) {
println(text)
}
fun caller() {
specials["callMe1"]?.invoke("print me")
specials["callMe2"]?.invoke(123)
}
fun main(args: Array<String>) {
caller()
}
My requirement is simple, I want to save the function asParam1 and asParam2 as a value in the variable specials. And invoke it later on by fetching the value from a Map.
However, the compiler doesn't like it:
Error:(1, 40) Type inference failed. Expected type mismatch: inferred
type is Map Unit> but Map Unit> was
expected
Error:(1, 69) No value passed for parameter num
Error:(1, 96) No value passed for parameter text
While this task is pretty simple in a weak typed language, I don't know how to do in Kotlin. Any help would be welcome. Thanks!
The correct syntax is "calllme" to ::asParam1.
But then the signatures will be wrong because the Map expects type (Any)->Unit and yours have (Int)->Unit and (String)->Unit. Here is an example that does not produce the error:
val specials:Map<String, (Any)->Unit> = mapOf(
"callMe1" to ::asParam1,
"callMe2" to ::asParam2
)
fun asParam1(num:Any) {
if(num is Int) println(num)
}
fun asParam2(text:Any) {
if(text is String) println(text)
}
fun caller() {
specials["callMe2"]?.invoke("print me")
specials["callMe1"]?.invoke(123)
}
Keep in mind, your code for the caller has special knowledge about how to call each of your functions (i.e., the correct parameter types), but the compiler does not have this same knowledge. You could accidentally call asParam1 passing a String instead of an Int (which is what your caller function was doing, I fixed it in my example) and that is not allowed. Which is why I changed the signatures of both asParam* to accept Any parameter, and then validated the expected type in each function (ignoring bad types).
If your intent is to pass integers in addition to strings to asParam2(), then change the body to test for both Int and String and convert the integer to a string.
When you write { asParam1() }, you create a lambda with an executable code block inside it, so you need to properly call the function asParam1(...), which requires an Int argument.
So, the first change you need to make is: { i -> asParam1(i) }.
But this code will still not pass the type checking, because, matching the type of the map, the lambda will be typed as (Any) -> Unit (the values in the map should all be able to accept Any, and a function that expects a narrower type cannot be a value in this map).
You then need to convert the Any argument to Int to be able to invoke the function: { i -> asParam1(i as Int) }
Finally, the map will look like this:
val specials: Map<String, (Any) -> Unit> = mapOf(
"callMe1" to { i -> asParam1(i as Int) },
"callMe2" to { s -> asParam2(s as String) }
)
The invocation stays unchanged, as in your code sample.
The function reference syntax (::asParam1) would allow you to reference a function that already accepts Any, it would not implicitly make the conversion described above. To use it, you would have to modify your functions to accept Any, as in #Les's answer.