I'd like to have an interface and implementing class/object similar to the following:
interface EventBus {
suspend fun <T : Message> publish(message: T)
suspend fun <R : Command, S : CommandResponse> request(command: R): Either<EventbusError, S>
suspend fun close(): Either<EventbusError, Unit>
//? fun <T : Message> subscribe(): Flow<T>
}
object EventBusImpl : EventBus {
private val _eventBus = MutableSharedFlow<Message>()
val messages = _eventBus.asSharedFlow()
override suspend fun <T : Message> publish(message: T) {}
override suspend fun <R : Command, S : CommandResponse> request(command: R): Either<EventbusError, S> {}
override suspend fun close(): Either<EventbusError, Unit> {}
inline fun <reified T:Message> subscribe():Flow<T> = messages.filterIsInstance<T>()
}
I understand that inline functions cannot be over overridden and thus cannot be part of an interface, but as the subscribe() function is an important part of the API, I'd still like to represent it somehow in the interface, without falling back to passing a Class<T> as an argument.
How could this be accomplished?
This
interface EventBus {
suspend fun <T : Message> publish(message: T)
suspend fun <R : Command, S : CommandResponse> request(command: R): Either<EventbusError, S>
suspend fun close(): Either<EventbusError, Unit>
suspend fun <T : Message> subscribe(type: Class<T>): Flow<T>
}
of course works, but is not very Kotlin'ish
Related
I am trying to implement a generic HttpClient like this one:
interface HttpClient {
fun <T: Any> get(url: String): T?
}
implemented by a class like this:
class HttpClientImpl #Inject constructor(...) : HttpClient {
override fun <T : Any> get(url: String): T? = execute(url)
private inline fun <reified T: Any> execute(url: String): T? {
val request = Request.Builder().url(url).get().build()
client.newCall(request).execute().use {
return it.body?.parseBodySuccess()
}
}
private inline fun <reified T: Any> ResponseBody?.parseBody(): T? {
val type = objectMapper.typeFactory.constructType(T::class.java)
return this?.let { objectMapper.readValue(it.string(), type) }
}
}
Now, I would like to be able to call such GET method in this way:
data class MyEntity(...)
class MyService #Inject constructor(private val client: HttpClient) {
fun performGet(url: String): MyEntity? = client.get<MyEntity>(url)
}
However this is not allowed and the compiler throws an error referring to the line of code
override fun <T : Any> get(endpoint: String): T? = execute(endpoint)
flagging that : Cannot use 'T' as reified type parameter. Use a class instead.
I have been trying to re-write the line as
override inline fun <reified T : Any> get(endpoint: String): T? = execute(endpoint)
however, despite having to make the other two inline functions "non private" the compiler still won't compile because in this last way of writing the overriding function, it says:
Override by a function with reified type parameter
How can I achieve such generic function?
I ended up doing something like this:
interface HttpClient {
fun <T: Any> get(url: String, type: Class<T>): T?
}
implemented as:
class HttpClientImpl #Inject constructor(...) : HttpClient {
override fun <T : Any> get(url: String, type: Class<T>): T? = execute(url, type)
private fun <T: Any> execute(url: String, type: Class<T>): T? {
val request = Request.Builder().url(url).get().build()
client.newCall(request).execute().use {
return it.body?.parseBody(type)
}
}
private fun <T: Any> ResponseBody?.parseBody(type: Class<T>): T? {
val dataType = objectMapper.typeFactory.constructType(type)
return this?.let { objectMapper.readValue(it.string(), dataType) }
}
}
that I can call in this way:
data class MyEntity(...)
class MyService #Inject constructor(private val client: HttpClient) {
fun performGet(url: String): MyEntity? = client.get(url, MyEntity::class.java)
}
I would have preferred to pass the Type directly as an actual type like
client.get<MyEntity>(url)
rather than passing the Class as a parameter, however, just for now it works...
If anyone can suggest a better way of doing this, please let me know.
Updated
As suggested by Pawel, I have created an extra inline extension function to the HttpClient interface
inline fun <reified T:Any> HttpClient.get (url: String) = get(url, T::class.java)
And I'm now able to call the function the way I wanted.
I'm coming from Java and am new to Kotlin. I try to understand how to use receiver type with lambdas specified as functional SAM interfaces.
Let the code speak for itself.
fun interface Delegator <T> {
fun delegate(receiver: T)
}
fun <T> invokeWithDynamicReceiver(receiver: T, fn: T.() -> Unit) = receiver.fn()
fun <T> invokeWithSamInterface(receiver: T, fn: Delegator<T>) = fn.delegate(receiver)
fun dynamicReceiver() {
invokeWithDynamicReceiver("Foo") { length } // Dynamic receiver
invokeWithSamInterface("Foo") { it.length } // Can't bind receiver as "this"
}
How do I need to change the code to use the Delegator lambda with dynamic receiver?
You can define the functions inside the Delegator as extension function, this way the receiver is passed as this to the lambda.
fun interface ExtensionDelegator <T, R> {
fun T.delegate(): R
}
fun <T, R> invokeWithExtensionSamInterface(receiver: T, fn: ExtensionDelegator<T, R>): R =
with(fn) { receiver.delegate() }
Alternatively, you can simply define the dynamic receiver with a typealias to achieve the same result.
typealias AliasDelegator<T, R> = T.() -> R
fun <T, R> invokeWithAliasSamInterface(receiver: T, fn: AliasDelegator<T, R>): R = fn.invoke(receiver)
On the use site, both approaches look the same.
fun main() {
val result = invokeWithExtensionSamInterface("Foo") { length }
println(result)
val otherResult = invokeWithAliasSamInterface("Fizz") { length }
println(otherResult)
}
I have this sealed class
sealed class S3FileUploaderRequest {
abstract val s3Configuration: S3Configuration
data class S3UploadRequest(
val uploadData: S3UploadData,
override val s3Configuration: S3Configuration
) : S3FileUploaderRequest()
data class S3DeleteRequest(
val path: String,
override val s3Configuration: S3Configuration
) : S3FileUploaderRequest()
data class S3MultiUploadRequest(
val multiUploadData: List<S3UploadData>,
override val s3Configuration: S3Configuration
) : S3FileUploaderRequest()
data class S3MultiDeleteRequest(
val paths: List<String>,
override val s3Configuration: S3Configuration
) : S3FileUploaderRequest()
}
And this interface:
interface FileUploader<T, S> {
suspend fun multiUploadFile(request: T): S
suspend fun multiDeleteFile(request: T): List<S>
suspend fun uploadFile(request: T): S
suspend fun deleteFile(request: T): S
}
I would like to use the child classes of data class S3FileUploaderRequest es entry parameter of interface functions, somethig like this:
interface FileUploader<T, S> {
suspend fun multiUploadFile(request: out T): out S
suspend fun multiDeleteFile(request: out T): List<S>
suspend fun uploadFile(request: out T): out S
suspend fun deleteFile(request: out T): out S
}
Is possible to do this? I feel like kotlin should be able to do this but don't find the proper syntax for it.
Update:
I would like to implement the interface this way:
#Singleton
class S3FileUploader() : FileUploader<S3FileUploaderRequest, S3FileUploaderResponse> {
override suspend fun uploadFile(request: S3UploadRequest): S3UploadResponse {
//DO STUFF
}
override suspend fun deleteFile(request: S3DeleteRequest): S3DeleteResponse {
//DO STUFF
}
override suspend fun multiUploadFile(request: S3MultiUploadRequest): S3MultiUploadResponse {
//DO STUFF
}
override suspend fun multiDeleteFile(request: S3MultiDeleteRequest): List<S3DeleteResponse> {
//DO STUFF
}
}
I am starting to learn Coroutines
what this code will look like on Coroutines ?
I would like to leave RX
and start using Coroutines
open class DataState<out T>(val newState: T, val oldState: T?, val json: String)
object EventBus {
private val dataPublisher: MutableMap<String, PublishSubject<DataState<Any>>> = mutableMapOf()
inline fun <reified T : Any> fire(event: DataState<T>) = fire(T::class.java.name, event)
fun <T : Any> fire(clazz: Class<T>, event: DataState<T>) = EventBus.fire(clazz.name, event)
fun <T : Any> fire(className: String, event: DataState<T>) {
synchronized(this) {
dataPublisher[className]?.onNext(event)
}
}
inline fun <reified T : Any> on(): Observable<DataState<T>> = on(T::class.java)
fun <T> on(dataType: Class<T>): Observable<DataState<T>> {
synchronized(this) {
var pub = dataPublisher[dataType.name]
if (pub == null) {
pub = PublishSubject.create()
dataPublisher[dataType.name] = pub
}
return pub.ofType(DataState::class.java) as Observable<DataState<T>>
}
}
fun reset() {
synchronized(this) {
dataPublisher.values.forEach { it.onComplete() }
dataPublisher.clear()
}
}
}
I really have no idea how to do the same thing with Coroutines, but I'm willing to entertain the possibility that it will be an improvement. Could someone show me how this would be done with Coroutines, and explain what's better about the Coroutines way?
I'm trying to write a method that takes a KProperty1 and a object of R like so
inline fun <T: Any, R: Any> List<T>.test(prop1: KProperty1<T, R>, prop2: R): List<T>
except I'm not getting type checking on the prop2. Is there any way to ensure that prop2 is of type R?
Here is a more complete example
class Foo
class Bar(val foo: Foo)
fun main(args: Array<String>): Unit {
val list = listOf(Bar(Foo()))
list.test(Bar::foo, Foo()) // This should work
list.test(Bar::foo, "") // I want this to be a type error since a string is not a Foo
}
inline fun <T: Any, R: Any> List<T>.test(prop1: KProperty1<T, R>, prop2: R): List<T> {
println(prop1.invoke(this.first())::class == prop2::class)
return listOf()
}
If you want to restrict R to children of Foo then provide upper bound constraint:
inline fun <T: Any, R: Foo> List<T>.test(prop1: KProperty1<T, R>, prop2: R): List<T> {
println(prop1.invoke(this.first())::class == prop2::class)
return listOf()
}