Kotlin: Out-projected type issue


#1

Please I need the help resolving the issue in below scenario

object Program {
    @JvmStatic
    fun main(args: Array<String>) {
        println(CAL.eval(ADD(LIT(5.0),LIT(2.0))))
    }
}    
interface CALD
data class LIT(val v:Double):CALD
data class ADD(val x:CALD, val y:CALD):CALD

interface IEvalHandler<in T:CALD> {
    fun evalOpp(v:T,f:(CALD)->Double):Double
}
class LitEvalHandler : IEvalHandler<LIT> {
    override fun evalOpp(v: LIT, f: (CALD) -> Double): Double = v.v
}
class AddEvalHandler : IEvalHandler<ADD> {
    override fun evalOpp(v: ADD, f: (CALD) -> Double): Double = f(v.x) + f(v.y)
}

val calMap = mapOf(LIT::class to LitEvalHandler(), ADD::class to AddEvalHandler())
object CAL {
    fun eval(opp:CALD):Double = calMap[opp::class]?.evalOpp(opp,CAL::eval)?:0.0 // Error
    // Error:(35, 35) Kotlin: Out-projected type 'IEvalHandler<*>?' prohibits the use of 'public abstract fun evalOpp(v: T, f: (CALD) -> Double): Double defined in IEvalHandler'
}

Edit 1: I have found below fix, but is there a better way to safe cast it
@Suppress(“UNCHECKED_CAST”)
fun eval(opp:CALD):Double = calMap[opp::class]?.let{ (it as IEvalHandler).evalOpp(opp,CAL::eval) }?:0.0


#2

I’m sorry if my answer is confusing, but here it goes.

What’s the common ancestor of LitEvalHandler and AddEvalHandler?
You might expect it to be IEvalHandler, but it’s not, since neither object can accept a CALD in their evalOpp method.
Maybe counter intuitively, IEvalHandler could be considered a subtype of IEvalHandler , since its evalOpp(v: CALD, f: (CALD) -> Double) can actually handle LITs, but not the other way around.

So, the compiler figures out that, since LitEvalHandler and AddEvalHandler are both IEvalHandler, it will let you use any method of IEvalHAndler that returns something of type T, because it’s safe to assume it will some kind of CALD.
However, anything the requires a T to be thrown in (as a function argument) cannot be used. This is because those methods require a specific kind of CALD and the compiler cannot guarantee you’re calling it with the right one.

The thing (I think) you’re trying to do can be easily done this way:

interface CALD {
    fun evalOpp(f: (CALD) -> Double): Double
}
data class LIT(val v: Double) : CALD {
    override fun evalOpp(f: (CALD) -> Double) = v
}
data class ADD(val x: CALD, val y: CALD) : CALD {
    override fun evalOpp(f: (CALD) -> Double) = f(x) + f(y)
}

where each kind of CALD is responsible for evaluating itself. It’s a object oriented solution.

Or this way:

sealed class  CALD
data class LIT(val v: Double) : CALD()
data class ADD(val x: CALD, val y: CALD) : CALD()

fun evalOpp(opp: CALD, f: (CALD) -> Double) = when (opp) {
    is LIT -> opp.v
    is ADD -> f(opp.x) + f(opp.y)
}

which is more like you do in functional programming languages.


#3

Thanks @ilogico