Is there a way to reduce/remove the redundant generic parameter in Kotlin? - kotlin

Consider the following function:
abstract class ObservableCollection<E> : CoroutineScope, MutableCollection<E> {...}
class ObservableList<E>(override val backingSource: MutableList<E> = mutableListOf()) : ObservableCollection<E>(), MutableList<E> by backingSource {...}
inline fun <reified T : Event<E>, E> ObservableCollection<E>.on(scope: CoroutineScope = this, noinline consumer: suspend (T) -> Unit): Job =
events.buffer(Channel.UNLIMITED)
.filterIsInstance<T>().onEach {
runCatching { consumer(it) }.onFailure { ObservableCollection.logger.catching(it) }
}.catch { ObservableCollection.logger.catching(it) }
.launchIn(scope)
To call it, the first and general way (which comes in mind):
val list = ObservableList<Int>()
list.on<ElementAddEvent<Int>, Int> {
}
To make it more idiomatic (towards removing redundancy of passing explicit generics), we can have something like this:
list.on { event: ElementAddEvent<Int> ->
}
But is there a way to remove that explicit generic type <Int> in { event: ElementAddEvent<Int> -> as well? Since it is implicit, as coming from the type of ObservableList on which on method/function is called on.
Edit:
Event<E> is basically an interface to for all the possible events like ElementRemoveEvent<E>, ElementAddEvent<E>, ElementReplaceEvent etc.
interface Event<E> : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = collection.coroutineContext
val collection: ObservableCollection<E>
}
class ElementAddEvent<E>(
val element: E,
override val collection: ObservableCollection<E>
) : Event<E>
class ElementReplaceEvent<E>(
val newElement: E,
val oldElement: E,
override val collection: ObservableCollection<E>
) : Event<E>
// And so-on...
It has basically nothing to do with the question, so I didn't put it on.
Edit 2:
One way could be to make static functions in each of the event like:
class ElementAddEvent<E>(
val element: E,
override val collection: ObservableCollection<E>
) : Event<E> {
companion object {
fun <E> on(collection: ObservableCollection<E>, consumer: suspend (ElementAddEvent<E>) -> Unit) {
collection.on { event: ElementAddEvent<E> ->
consumer(event)
}
}
}
}
This will make possible to be callable as:
ElementAddEvent.on(list) {
}
But I think this will make too much noise in the sources, so I would prefer to use the second approach posted in the question itself (specifying type inside the lambda). If I missed something question is still open, you can let me know!

Related

Kotlin put children class type into parent class type

I'm trying to have an object that collects the listeners for the childrent class of Message class.
typealias Listener <T> = (T) -> Unit
object MessageRegistry {
private val listeners = mutableMapOf<KClass<out Message>, MutableSet<Listener<Message>>>()
fun <T : Message> listen(clazz: KClass<T>, listener: Listener<T>) {
listeners.getOrPut(clazz) { mutableSetOf() }.add(listener)
}
}
I want the clients of this object to register their listeners like the following when ConnectedMessage is a subtype of Message:
MessageRegistry.listen(ConnectedMessage::class) { connectedMessage: ConnectedMessage ->
doSomething(connectedMessage)
}
However, there seems to be a problem in the MessageRegistry.listen() method.
I can't get listener added to the MutableSet<Listener<Message>>>.
The listener has a type (T) -> Unit with a generic T being <T: Message> and
I want to added to the MutableSet with the type (Message) -> Unit.
I'm getting a type error as follows:
Type mismatch.
Required:
Listener<Message> /* = (Message) → Unit */
Found:
Listener<T> /* = (T) → Unit */
As I mentioned that the T inherits the Message class (<T : Message>), I think this is a valid conversion.
I've also tried marking out to the generic type of the Listener:
private val listeners = mutableMapOf<KClass<out Message>, MutableSet<Listener<out Message>>>()
but it gives the following error:
Conflicting projection in type alias expansion in intermediate type '(T) -> Unit'
What can I do to have the listeners in a set?
Thanks in advance.
Your Listener is a T consumer, not producer, so it cannot be given a type of out Message and still be useful.
Since your function allows subtypes of Message for the Listener, but Listener is a consumer, your Listener cannot satisfy the requirement to be a Listener<Message>. For example, if you had a Listener<SubMessage> and tried to pass a Message to it, it would be unsafe because the Message might not be a SubMessage.
However, since you are storing these in a Map with the class, you are effectively creating runtime-safety of the types, and you can logically deduce types are safe to cast if they match up.
Therefore, the generics system cannot guarantee safety, but you can if you are careful about where you cast. You can implement it like this:
object MessageRegistry {
private val listeners = mutableMapOf<KClass<out Message>, MutableSet<Listener<*>>>()
fun <T : Message> listen(clazz: KClass<T>, listener: Listener<T>) {
listeners.getOrPut(clazz) { mutableSetOf() }.add(listener)
}
private fun <T: Message> getListeners(clazz: KClass<T>): MutableSet<Listener<T>> {
#Suppress("UNCHECKED_CAST")
return listeners[clazz] as MutableSet<Listener<T>>
}
}
I suggest making these inline/reified to make your code simpler at the call sites. In most cases, it will be able to infer the type at the call site so you won't have to explicitly pass it.
object MessageRegistry {
private val listeners = mutableMapOf<KClass<out Message>, MutableSet<Listener<*>>>()
#PublishedApi
internal fun <T : Message> listen(clazz: KClass<T>, listener: Listener<T>) {
listeners.getOrPut(clazz) { mutableSetOf() }.add(listener)
}
inline fun <reified T : Message> listen(noinline listener: Listener<T>) {
listen(T::class, listener)
}
private fun <T: Message> getListeners(clazz: KClass<T>): MutableSet<Listener<T>> {
#Suppress("UNCHECKED_CAST")
return listeners[clazz] as MutableSet<Listener<T>>
}
}

Kotlin DSL builders generalization

While writing DSL in Kotlin for an API with lots of classes I'd encountered an issue. Code seemed completely boilerplate to me. Is there a way to generalize code in DSL builders?
Suppose we have an API:
class Robot(val name: String, val head: Head, val body: Body, val hands: Hands, ...)
class Head(val mouth: Mouth, ... )
class Body(val material: Material, ... )
class Hands(...)
class Mouth(...)
class Material(...)
The classes dependency tree could be much larger than that.
DSL for this api would look something like:
fun robot(name: String, settings: RobotBuilder.() -> Unit) = RobotBuilder(name).apply(settings).build()
interface Builder<T> {
fun build(): T
}
#RobotDsl
class RobotBuilder(val name: String) : Builder<Robot> {
private var head: Head = DefaultBotParts.head
private var body: Body = DefaultBotParts.body
private var hands: Hands = DefaultBotParts.hands
...
fun head(initHead: HeadBuilder.() -> Unit) {
head = HeadBuilder().apply(initHead).build()
}
fun body(initBody: BodyBuilder.() -> Unit) {
body = BodyBuilder().apply(initBody).build()
}
fun hands(initHands: HandsBuilder.() -> Unit) {
body = HandsBuilder().apply(initHands).build()
}
...
override fun build(): Robot = Robot(name, head, hands)
}
#RobotDsl
class HeadBuilder : Builder<Head> {
private var mouth: Mouth = DefaultBotParts.mouth
fun mouth(init: MouthBuilder.() -> Unit) {
mouth = Mouth().apply(init).build()
}
override fun build(): Robot = Head(mouth)
}
#RobotDsl
class MouthBuilder : Builder<Mouth> {
//its properties and dsl functions
...
override fun build(): Mouth = Mouth(...)
}
I think the problem is clear now. Each builder can have one or more dependent ones, and they only needed for declaring dsl functions and scope separation, the code is boilerplate.
So, can there be a simple solution that generalizes the code?

Kotlin: generate a Factory by class

We're trying to do some generic processing in kotlin. Basically, for a given class, we want to get the related Builder object. i.a. for any object that extends a GenericObject, we want a Builder of that Object.
interface Builder<T : GenericObject>
object ConcreteBuilder: Builder<ConcreteObject>
We'd need a function that will return ConcreteBuilder from ConcreteObject
Our current implementation is a Map:
val map = mapOf<KClass<out GenericObject>, Builder<out GenericObject>>(
ConcreteObject::class to ConcreteBuilder
)
Then we can get it with:
inline fun <reified T : GenericObject> transform(...): T {
val builder = map[T::class] as Builder<T>
...
However this isn't very nice as:
we need an explicit cast to Builder<T>
the map has no notion of T, a key and a value could be related to different types.
Is there any better way to achieve it?
A wrapper for the map could be:
class BuilderMap {
private val map = mutableMapOf<KClass<out GenericObject>, Builder<out GenericObject>>()
fun <T: GenericObject> put(key: KClass<T>, value: Builder<T>) {
map[key] = value
}
operator fun <T: GenericObject> get(key: KClass<T>): Builder<T> {
return map[key] as Builder<T>
}
}
This hides the ugliness, while not completely removing it.
To use:
val builderMap = BuilderMap()
builderMap.put(ConcreteObject::class, ConcreteBuilder)
builderMap.put(BetonObject::class, BetonBuilder)
// builderMap.put(BetonObject::class, ConcreteBuilder) – will not compile
val builder = builderMap[T::class]

Kotlin: access companion data in generic function

I have a simple task in Kotlin of listing enum values:
interface DisplayableEnum<E: Enum<E>> {
val displayValue: String
}
inline fun <reified T> printAllValues(selected: T?) where T: Enum<T>, T: DisplayableEnum<T> {
println("All Values:")
enumValues<T>().forEach {
println(it.displayValue)
}
selected?.let { println("\nSelected: ${it.displayValue}") }
}
////// USAGE
enum class MyEnum(override val displayValue: String): DisplayableEnum<MyEnum> {
A("value is A"),
B("value is B")
}
fun main() {
// with a selected value
f(MyEnum.A)
// without a selected value
f(null as MyEnum?)
}
Now imagine that all the enums I pass to printAllValues should also have a field called defaultValue. In case of MyEnum I would write it the following way:
enum class MyEnum(override val displayValue: String): DisplayableEnum<MyEnum> {
A("value is A"),
B("value is B");
companion object {
val defaultValue = A
}
}
So my question: is there a way to define such a contract in Kotlin?
Ideally, I would like to somehow define that contract in an interface, like DisplayableEnum above, and then somehow use it in printAllValues like this:
inline fun <reified T> printAllValues(selected: T) where T: Enum<T>, T: DisplayableEnum<T> {
println("All Values:")
enumValues<T>().forEach {
println(it.displayValue)
}
selected?.let { println("\nSelected: ${it.displayValue}") }
println("Default value: ${T.defaultValue???}"
}
The one thing that I don't want is using non-companion defaultValue, I always have to either pass it to the function manually (but why if the type contains all the info?) or, if made non-companion:
interface DisplayableEnum<E: Enum<E>> {
val displayValue: String
val defaultValue: E
}
then access it through an object - do something ugly like enumValues<T>().first().defaultValue.
I wonder if Kotlin has a solution in this case.
It's impossible to define abstract properties in companion object.
So it should be defined in interface directly.
The tricky part here is implementing of this interface without compiler warnings:
enum class MyEnum(override val displayValue: String) : DisplayableEnum<MyEnum> {
A("value is A"),
B("value is B");
override val defaultValue: MyEnum by lazy { A }
}
If enumValues<T>().first().defaultValue looks ugly to you, wrap it into auxilary function:
inline fun <reified T> enumDefaultValue(): T where T : Enum<T>, T : DisplayableEnum<T> =
enumValues<T>().first().defaultValue
//Usage:
println("Default value: ${enumDefaultValue<T>()}")

Kotlin type inference on "supposedly" right types

I am new to Kotlin and I was playing with it. I pretty much wanted to create a pretty basic event bus. So I came up with this
interface Event
interface EventListener<E : Event> {
fun handle(event: E)
}
interface EventBus {
fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>)
}
class MyBus() : EventBus {
private val eventListeners: MutableMap<String, MutableList<EventListener<out Event>>> = mutableMapOf()
constructor(listeners: List<Pair<Class<Event>, EventListener<Event>>>) : this() {
listeners.forEach {
registerListener(it.first, it.second)
}
}
override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
val key = aClass.name
val listeners: MutableList<EventListener<out Event>> = eventListeners.getOrPut(key) { mutableListOf() }
listeners.add(eventListener)
}
}
val bus = MyBus(
listOf(
MyEvent::class.java to MyEventListener()
)
)
class MyEvent : Event
class AnotherEvent : Event
class MyEventListener : EventListener<MyEvent> {
override fun handle(event: MyEvent) {
}
}
what happens is that when I try to create MyBus using the constructor accepting the list of pairs, I get
Type inference failed. Expected type mismatch: inferred type is List<Pair<Class<MyEvent>,MyEventListener>> but List<Pair<Class<Event>,EventListener<Event>>> was expected
But if I change the constructor to be something like
constructor(listeners: List<Pair<Class<out Event>, EventListener<out Event>>>) : this() {
listeners.forEach {
registerListener(it.first, it.second)
}
}
adding out pretty much everywhere, then the MyBus constructor works, but the invocation to registerListener(..) breaks for the same exact reason as before. So the only way to solve this is to add "out"s also on registerListener function.
I suspect I'm doing something wrong here, but I don't know what precisely. Any help?
If you want your EventListener to be able to consume Events, then its type has to be invariant or covariant (not declared out). If it let you pass your EventListener<MyEvent> as if it were an EventListener<Event>, then your MyBus class might call listener.handle(event) on it with some Event that is not a MyEvent, such as AnotherEvent. Then you will get a ClassCastException when it tries to cast this AnotherEvent to MyEvent.
To be able to store different types of invariant EventHandlers, you will have to remove the variance restrictions by using star projection, and cast them when you retrieve them from the map. So make the map keys into class objects instead of just Strings. Since you will not have the help of the compiler when working with the star-projected types, you need to be careful that you are only adding an item to your MutableMap that is of the same type as the Class key that's associated with it. Then when you retrieve items, only cast to an invariant type.
The other part of your issue is that your constructor needs a generic type. Right now it works exclusively with Event so it can't handle subtypes of Event. Kotlin doesn't (yet?) support generic types for constructors so you have to do this with a factory function.
Here's an example of all the above.
class MyBus() : EventBus {
private val eventListeners: MutableMap<Class<*>, MutableList<EventListener<*>>> = mutableMapOf()
override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
val listeners = retrieveListeners(aClass)
listeners.add(eventListener)
}
private fun <E: Event> retrieveListeners(aClass: Class<E>): MutableList<EventListener<E>> {
#Suppress("UNCHECKED_CAST")
return eventListeners.getOrPut(aClass) { mutableListOf() } as MutableList<EventListener<E>>
}
}
// Factory function
fun <E : Event> myBusOf(listeners: List<Pair<Class<E>, EventListener<E>>>): MyBus {
return MyBus().apply {
listeners.forEach {
registerListener(it.first, it.second)
}
}
}
And you might want to change the type of the factory parameter from a <List>Pair to a vararg Pair so it's easier to use.
Here's a stripped down example to explain the variance limitation.
Your interface for an Event consumer:
interface EventListener<E : Event> {
fun handle(event: E)
}
Two implementations of Event:
class HelloEvent: Event {
fun sayHello() = println("Hello world")
}
class BoringEvent: Event {}
A class implementing the interface:
class HelloEventListener: EventListener<HelloEvent> {
override fun handle(event: HelloEvent) {
event.sayHello()
}
}
Now you have an EventListener that can handle only HelloEvents. Try to treat it like an EventListener<Event>:
val eventListener: EventListener<Event> = HelloEventListener() // COMPILE ERROR!
Imagine the compiler did not prevent you from doing this and you do this:
val eventListener: EventListener<Event> = HelloEventListener()
eventListener.handle(BoringEvent()) // CLASS CAST EXCEPTION AT RUN TIME!
If this were allowed your HelloEventListener would try to call sayHello() on the BoringEvent, which doesn't have that function, so it will crash. This is what generics are here to protect you from.
Now suppose your HelloEventListener.handle() didn't call event.sayHello(). Well, then it could have safely handled a BoringEvent. But the compiler isn't doing that level of analysis for you. It just knows what you declared, that HelloEventListener cannot handle anything except HelloEvent.