Using Kotlin contracts to cast type inside Iterable function predicate

为君一笑 提交于 2020-05-24 03:29:02

问题


I have this sealed class PictureEvent:

sealed class PictureEvent {

    data class PictureCreated(val pictureId: String, val url: String) : PictureEvent()

    //more classes extending PictureEvent

}

Now, from a list of PictureEvents, I want to get the first PictureCreated:

fun doSomething(events: List<PictureEvent>) {

  val creationEvent = events.first { isCreationEvent(it) } as PictureEvent.PictureCreated

  //do stuff with the creationEvent

}

private fun isCreationEvent(event: PictureEvent) : Boolean {
  return event is PictureEvent.PictureCreated
}

It works fine. As you see, I'm casting the event to PictureCreated (using as keyword), since first method, returns a PictureEvent. I wonder if it's possible to avoid this casting by using Kotlin contracts.

I have tried this:

private fun isCreationEvent(event: PictureEvent) : Boolean {
  contract {
    returns(true) implies (event is PictureEvent.PictureCreated)
  }
  return event is PictureEvent.PictureCreated
}

But it doesn't work; first method keep returning a PictureEvent, instead of PictureCreated. Is it possible to do this currently?


回答1:


The contract works fine, however if you take a look at the first method signature, you should be able to understand what is happening and why the found object isn't autocast:

public inline fun <T> Iterable<T>.first(predicate: (T) -> Boolean): T

The return type of the first method is just the same as that defined for all elements in the Iterable instance, PictureEvent in your case, and no autocasting inside the predicate can unfortunately change that.


Instead of contracts, for example, you can filter your list by the desired class type first, and then take the first element:

val creationEvent = events
    .filterIsInstance(PictureEvent.PictureCreated::class.java)
    .first()

or create your own extension similar to first:

inline fun <reified R> Iterable<*>.firstOfInstance(): R {
    val first = first { it is R }
    return first as R
}

// or wrapping filterIsInstance
inline fun <reified R> Iterable<*>.firstOfInstance(): R {
    return filterIsInstance(R::class.java).first()
}

val creationEvent = events.firstOfInstance<PictureEvent.PictureCreated>()


来源:https://stackoverflow.com/questions/61639584/using-kotlin-contracts-to-cast-type-inside-iterable-function-predicate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!