1
votes

Today while programming I found some odd behaviour in Kotlin. I could easily go around it, but I wonder if there is some reason to it or if it is a bug in Kotlin.

I have the following interface of a delegate which delegates the showing of a dialog to the Activity.

interface ViewModelDelegate {
fun showWarningDialog(textResource: Int)
}

I want to implement it as following in the Activity. Since I know I can only do it with a context and the Activity.getContext() may return null, I wrap the code in context?.let

override fun showWarningDialog(textResource: Int) = context?.let {
//show dialog
}

However this gives me a compile error:

Return type of 'showWarningDialog' is not a subtype of the return type of the overridden member 'public abstract fun showWarningDialog(textResource: Int): Unit defined in com.some.class.path'

Which really confused me, because I don't want to return anything. So since let returns whatever the function inside returns, I was wondering if I could fix it by writing a version of let which does not return anything.

fun <T, R> T.myLet(block: (T) -> R) {
    let(block)
}

However this did not remove the compiler error. I found then that the mouseover text over the error gives more information (would be nice if the compiler did). It says:

Return type is 'Unit?', which is not a subtype of overridden

Now that tells me more about the problem. Because the function context?let call may not happen, it could return null. Now there are multiple ways to go around this. I could add ?: Unit too the end of the function call or I could define showWarningDialog to return Unit? which will allow me to call it just fine in most cases. However none of these solutions are desireable. I will probably just make a normal method and call the let inside of that instead of delegating the call to it. Costs me another level of indentation and an extra vertical line:

override fun showWarningDialog(textResource: Int) {
    context?.let {
        //show dialog
    }
}

My question is, is this behaviour intended? Why or when would this be useful that a function that returns Unit cannot be delegated to an optional function call. I am very confused by this behaviour.

2
Why you don't want to just write override fun showWarningDialog(textResource: Int) { context?.let { //show dialog } }Andrei Tanana
@AndreiTanana That is exactly what I meant that I will probably do now. But it is an extra level of intentation in the IDE and another line. As mentioned, I know how to solve it. but I am just very confused by this behaviour and why it exists in the first place.findusl

2 Answers

2
votes

Single expression function

fun foo() = <expression>

by language design is equivalent to

fun foo(): <ReturnType> {
 return <expression>
}

And because Unit? is not a not a subtype of Unit, you can't return it in from a function, which returns Unit. In this sense Unit just another type in the type system, it's not something magical. So it works just as it's supposed to work with any other type.

Why or when would this be useful that a function that returns Unit cannot be delegated to an optional function call.

So basically the question is why language designers did not created a special handling to accept Unit? from a function declaring Unit as a return type. I can think about a few reasons:

  1. It requires to create this special handling in the compiler. Special cases lead to bugs, break slim language design and complicate documentation.
  2. As it had to be a special case, it would be not really clear and predictable for programmers. Currently it works in the same way for all types, no special treatments. It makes the language predictable, you don't need to check the documentation for every type to see if it's treated specially.
  3. It also adds some additional safety, so to make you notice that your expression can actually skip the calculation.

So trying to summarize, I would say making this case work does not add much of value but can potentially bring some issues. That's probably why they did not add it to the language.

0
votes

lets discuss this case when you have return type for example String

interface someInterface{
    fun somFun():String
}
class someClass : someInterface {
    var someString:String? = null
    override fun somFun()=someString?.let { 
        //not working
        it
    }

    override fun somFun()=someString?.let {
        //working
        it
    }?:""
}

so what we see that when parents return type is String you cannot return Strin? it is jus kotlins nullSafety , what is different when you don't have return type ? lets change the code above a little

interface someInterface{
    fun somFun():String
    fun unitFun()
}
class someClass : someInterface {
    var someString:String? = null

    override fun unitFun() {
       //if it is possible to return null in here 
    }

    override fun somFun()=someString?.let {
        val someresult = unitFun().toString() //you will get crash
        it
    }?:""
}

now we have another function without return type (unitFun Unit) so if you can return Unit? in your subclass it will cause a crash when you want to use the result of method because it is defined asUnit and you dont need any null checks.

generally it means Unit is also type and you need to keep it null safe .