How to pass generically scoped suspend lambda to class method in Kotlin? - kotlin

Want to have a function inside an open class which can accept a suspend lambda and run it.
I know this works when you specify the type explicitly but if possible need it to accept generically scoped lambdas.
class ChildClass : SuperClass() {
// does work :)
fun launch(block: suspend ChildClass.() -> Unit) =
coroutineThing { this.block() }
}
open class SuperClass {
// doesn't work :(
fun <T : SuperClass> launch(block: suspend T.() -> Unit) =
coroutineThing { this.block() }
}
The error I am getting is Expression 'block' of type 'suspend T.() -> Unit' cannot be invoked as a function. The function 'invoke()' is not found.
Edit:
Looking to eventually call this method from an instance of the ChildClass like this: ChildClass().launch { doStuff() }

You can write like this but I don't know how it works :) For me, it looks like Kotlin issue.
open class SuperClass {
fun <T : SuperClass> launch(block: suspend T.() -> Unit) =
coroutineThing { block.invoke(this as T) }
}
but in that case you have to invoke it like this:
ChildClass().launch<ChildClass> { println("test") }
but you can change it a bit to invoke it like you want:
open class SuperClass<T : SuperClass<T>> {
fun launch(block: suspend T.() -> Unit) =
coroutineThing { block.invoke(this as T) }
}
class ChildClass : SuperClass<ChildClass>()
//invokation
ChildClass().launch { println("test") }

How about this ?
open class SuperClass {
fun<T: SuperClass> launch(superClass: T, block: suspend T.() -> Unit) {
GlobalScope.launch {
superClass.block()
}
}
}
open class ChildClass: SuperClass() {
suspend fun print() {
}
}
fun test () {
val a = ChildClass()
a.apply {
launch(this) {
print()
}
}
}

Related

Executing suspend function inside Iterable.sortedBy

I have this code:
interface Film {
suspend fun total(): Int
}
suspend fun getFilms() : List<Film> {
return films.sortedBy { it.total() }
}
But I get an error because I'm calling Film::total method inside non-suspension function (sortedBy selector). How can I solve this?
suspend fun getFilms(): List<Film> {
return films
.map { it to it.total() }
.sortedBy { it.second }
.map { it.first }
}
I guess it is just a warning, anyways you can force it to run using runBlocking
suspend fun getFilms() : List<Film> {
val films = arrayListOf<Film>()
return films.sortedBy { runBlocking { it.total() } }
}
The error states it clearly "Suspension functions can be called only within coroutine body". You are invoking total() from within sortedBy thus the error.
You should give more context in order for me to be able to provide a more accurate answer. Having such little context you can remove the suspend from total() to let the code compile. Anyway just try this:
suspend fun getFilms() = films.sortedBy { it.total() }
If you cannot change the interface what about something like that:
data class MyFilm(val total: Int) : Film {
override suspend fun total(): Int = total
}
suspend fun getFilms(): List<Film> = withContext(Dispatchers.Default) {
films.sortedBy { it.total }
}
You can wrap #IR42's answer in an extension function to make code a bit more readable if you use this in multiple places.
suspend inline fun <T, R : Comparable<R>> Iterable<T>.sortedBySuspending(
crossinline selector: suspend (T) -> R?
): List<T> = this
.map { it to selector(it) }
.sortedBy { it.second }
.map { it.first }

Interface with generic type of the class implementing the interface?

Having a class that implement a interface with generic type of "This" class
Is there a way to do this without a cast?
Simple code:
interface Triggerable<This: Triggerable<This>> {
var trigger: (This) -> Unit
fun triggerNow() = trigger(this as This)
}
class Test : Triggerable<Test>{
override var trigger: (Test) -> Unit = { /*...*/ }
}
The same a little more complex:
interface TriggerInterface<T> {
val trigger: (T) -> Unit
fun triggerNow()
}
interface Triggerable<T: Triggerable<T>>: TriggerInterface<T> {
override fun triggerNow() = trigger(this as T)
}
interface Signalable<T>: TriggerInterface<T> {
var value: T
override fun triggerNow() = trigger(value)
}
class Test : Triggerable<Test>{
override val trigger: (Test) -> Unit = { /*...*/ }
}
Should be possible like this
interface TriggerInterface<T: TriggerInterface<T>> {
val trigger: (T) -> Unit
fun triggerNow()
fun getThis(): T
}
interface Triggerable<T: TriggerInterface<T>>: TriggerInterface<T> {
override fun triggerNow() = trigger(getThis())
}
class Test : Triggerable<Test>{
override fun getThis(): Test = this
override val trigger: (Test) -> Unit = { /*...*/ }
}
Check http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ206
I would also personally recommend to reconsider if you really need both TriggerInterface AND Triggerable (where one inherits from the other).
Calling override fun triggerNow() = trigger(this as T) you are trying to cast Triggerable<T> to T thats why compiler warns you about unchecked cast
having
val trigger: (TriggerInterface<T>) -> Unit
will allow you to call trigger with no casting
override fun triggerNow() = trigger(this)
to actually execute
override val trigger: (Test) -> Unit = { /*...*/ }
you need an instance of Test to pass inside of trigger which is not declared anywhere in your code yet

Using lambdas does not compile when trying to pass in a method expecting a SAM interface

I am trying to understand lambdas and Kotlin. I created this trivial example
interface OnClickListener {
fun onClick(s: String)
}
class Button {
var clickListener: OnClickListener? = null
fun setOnClickListener(listener: OnClickListener?) {
clickListener = listener
}
fun click() {
clickListener?.onClick("hello")
}
}
fun main(args: Array<String>) {
val b = Button()
b.setOnClickListener(
object : OnClickListener {
override fun onClick(s: String) {
println(s)
}
}
)
/*
Variation 1
val l = {
s -> println(s)
}
b.clickListener = l*/
/*
Variation 2
b.setOnClickListener{
s -> println(s)
}
*/
/*
Variation 3
b.clickListener = {
s -> println(s)
}
*/
b.click()
}
So the above code only compiles if I pass an anonymous object. But I wanted to figure out how to use the lambdas.
None of the 3 variation to use a lambda compiles.
I thought since the OnClickListener is a SAM I should easily be able to pass in a lambda
What am I doing wrong here?
To be able to use a lambda, you need to use a Java interface.
First, create a Java file and create an interface:
public interface OnClickListener {
void onClick(String s);
}
Then in your main:
b.setOnClickListener(OnClickListener { s ->
println(s)
})
As for your Button class:
class Button {
var clickListener: OnClickListener? = null //You can use this too but there's another way as well.
//lateinit var clickListener: OnClickListener //Telling the compiler that you will initialize it later on.
fun setOnClickListener(listener: OnClickListener) { //removed redundant ? from the function signature.
clickListener = listener
}
fun click() {
clickListener?.onClick("hello") //Incase of lateinit, you don't need a '?' anymore
}
}
SAM conversion only works between a Java code and a Kotlin code.
EDIT: Since in Kotlin, you can store a function in a variable as well, here is my another two cents on how you can do it in a different way:
class Button {
lateinit var myFunction: (String) -> Unit
fun setOnClickListener(block : (String) -> Unit) {
myFunction = block //storing state of your 'listener'
}
fun onClick() = myFunction.invoke("Invoked from onClick function")
}
Then in your main:
fun main() {
val button = Button()
button.setOnClickListener { s ->
println(s)
}
button.onClick()
}
As Taseer Ahmad points out, SAM conversion only works for Java interfaces since Kotlin already has proper function types. Of course, an easy way around this is to simply define a second setOnClickListener method that takes a function type
class Button {
var clickListener: OnClickListener? = null
fun setOnClickListener(listener: OnClickListener?) {
clickListener = listener
}
inline fun setOnClickListener(crossinline listener: (String) -> Unit) {
setOnClickListener(object : OnClickListener {
override fun onClick(s: String) = listener(s)
})
}
fun click() {
clickListener?.onClick("hello")
}
}
This then allows you to write b.setOnClickListener { println(it) }. I always inline methods like this as a habit, but it's not really required, so you can remove the inline and crossinline if you want.

Invoking methods on interfaces with generics

The following is a very simple illustration of what I'm trying to do:
interface Event {
fun value(): Int
}
class Event1: Event {
override fun value() = 1
}
class Event2: Event {
override fun value() = 2
}
interface EventConsumer<T> where T: Event {
fun consume(event: T)
}
class Event1Consumer: EventConsumer<Event1> {
override fun consume(event: Event1) {
println(event.value())
}
}
class Event2Consumer: EventConsumer<Event2> {
override fun consume(event: Event2) {
println(event.value())
}
}
class EventManager {
private val consumers: Map<KClass<*>, EventConsumer<*>> = mapOf(
Event1::class to Event1Consumer(),
Event2::class to Event2Consumer()
)
fun consume(event: Event) {
val consumer = consumers[event::class]
consumer?.consume(event)
}
}
The final method call (consumer.consume()) is giving me a compiler error
Out-projected type 'EventConsumer<*>?' prohibits the use of 'public
abstract fun consume(event: T): Unit defined in EventConsumer'
I know that Kotlin is a lot more strict about generics than Java which is probably why it doesn't work, but how would I implement something like this properly?
Since you are building the consumers map, it would be safe to make an unchecked cast to the correct generic EventConsumer type:
fun <T: Event> consume(event: T) {
val consumer = consumers[event::class] as? EventConsumer<T>
consumer?.consume(event)
}

Replacing SAM-constructor with lambda with covariant type

I have got the following Java interfaces:
interface Action1<T> {
void call(T t);
}
interface Test<T> {
void test(Action1<? super T> action)
}
And the following Kotlin class:
interface A {
fun go()
}
abstract class Main {
abstract fun a(): Test<out A>
fun main() {
a().test(Action1 { it.go() })
a().test { it.go() }
}
}
Now in the function main, the first statement compiles, but IntelliJ gives a warning that the SAM-constructor can be replaced with a lambda.
This would result in the second statement.
However, this second statement does not compile, because it has type Any?, not A. Removing the out modifier makes it compile again.
Why does this happen?
The use case of this is when the implementing class of Main needs to return Test<B> for the function a(), where B implements A:
class B : A {
override fun go() {
TODO()
}
}
class MainImp : Main() {
override fun a(): Test<out A> {
val value: Test<B> = object : Test<B> {
override fun test(action: Action1<in B>?) {
TODO()
}
};
return value
}
}
It is a compiler bug. You can track it here: https://youtrack.jetbrains.com/issue/KT-12238.