Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Union type between Any? and Unit

Tags:

kotlin

I'm trying to write a higher order function that will allow me to do this:

blah1 { em ->
    // do some stuff here
}

as well as this:

blah2 { em ->
    // do some stuff here
    listOf("1","2","3")
}

In the first example, i'm returning nothing and in the second example I'm return a list of strings.

This works for the first one:

inline fun blah1(f: (em: EM) -> Unit): Result {
    f(em)
    return Result()
}

For the second one, I need a different return type:

inline fun blah2(f: (em: EM) -> Any?): Result {
    val res = f(em)
    when (res) {
        is List<*> -> res.forEach { item ->
            // do something with item
        }
    }
    return Result()
}

Is it possible to combine blah1 and blah2 so that if I do this:

blah { em ->

}

val res = f(em) res will be of type Unit and when I do this:

blah { em ->
   listOf("1","2","3")
}

val res = f(em) res will be of type List ?

I've tried doing this:

inline fun blah(
    noinline f1: (em: EM) -> Unit = f1fun,
    noinline f2: (em: EM) -> Any? = f2fun
): Result {
    f1(em)
    f2(em)?.let { res ->
        when (res) {
            is List<*> -> res.forEach { item ->
                // do something with item
            }
        }
    }
    return Result()
}

val f1fun: (em: EntityManager) -> Unit = { Unit }
val f2fun: (em: EntityManager) -> Any? = { null }

... but now whether I do

blah { em ->
    // code here    
}

or

blah { em ->
    // code here
    listOf("1", "2")
}

f2's lambda is always executed.

Is there a way to achieve having a blah { with or without statement } that will allow you to collect that value as Any? if a statement is indeed inside the lambda and when no statement is present, treat it like a Unit lambda?

This: inline fun blah2(f: (em: EM) -> Any?): Result {

doesn't compile under certain circumstances:

blah2 {
   k = k + 1
}

even though this works fine:

blah2 {

}

and this works fine:

blah2 {
  k++
}

Update: Have logged a bug in the meantime: https://youtrack.jetbrains.com/issue/KT-20215

like image 589
Jan Vladimir Mostert Avatar asked Dec 07 '25 18:12

Jan Vladimir Mostert


2 Answers

k = k + 1 is an assignment which is a statement not an expression in Kotlin and it therefore does not evaluate to Unit or any other type.


Why are assignments with no type at all taken into account when the compiler is looking for expressions?

Consider the following short program:

fun main(args: Array<String>) {
    val result = num { a ->
        var b = a * 5
        // ...put whatever code you want here
        // ...and here
        b++ // <- the last line matters
    }
}

fun num(f: (i: Int) -> Int): Int {
    return f(5)
}

The last line inside that lambda body, that's the line that matters, and it has to be an expression (Int in this case), because it is what this lambda returns.

So, can the last line be b = b + 1? No, because it is not an expression an therefore cannot be returned as such.

Even if you would use Any or Unit as return type doesn't change the fact that b = b + 1 is neither of those.

What I had to do to get around this was do this: blah2 { k = k + 1; Unit }, otherwise my lambda didn't work

Yes, because Unit is a valid expression and blah2 { k = k + 1; Unit } is the same as

blah2 { 
   k = k + 1 
   Unit // again, last line matters
}
like image 60
Willi Mentzel Avatar answered Dec 11 '25 10:12

Willi Mentzel


This should work for Kotlin:

inline fun blah2(f: (em: EM) -> Any?): Result {
    val res = f(EM())
    when (res) {
        is List<*> -> res.forEach { 
            // do something with item
            println("Item: " + it)               
        }
        is Unit -> {
            // do something else
        }
    }
    return Result()
}

fun tryit() {
    var res = blah2 {  }
    res = blah2 { arrayListOf<String>("A","b","c") }
}

But if you are trying to call your Kotlin blah2 from Java code, you may have problems. This link (Unit closures required to return Kotlin.Unit) describes a problem passing a function returning void from Java to a Kotlin method. I have not followed to see if it's solved.

Edit These should also work:

    var k: Int = 0
    res = blah2 { k++ }
    //res = blah2 { k = k + 1 }  // does not compile
    //res = blah2 { k = k + 1; return@blah2 } // does not compile
    res = blah2 { k = k + 1; return@blah2 k }

But the two commented lines do not work. The OP has written a bug report on this (and I agree that it's a bug). But, keep in mind that if it did work, the behavior would be different than what would be expected from Java. In Java, k = k + 1 is an expression, but in Kotlin it is not. Therefore, the return type of this expression is not Int. If it worked, the return type would be Nothing (see Return and Jumps). If SO were to port from Java to Kotlin, they need to watch out for "assignments as expressions". Here is an interesting discussion on this matter.

Update Here is further example of the inconsistency:

class TestLambdaReturnType {
    var k = 1;
    var a: () -> Any = {} //explicitly typed (accepts: () -> Unit )
    val b = { k=2 }       //compiler implicitly types b: () -> Unit
    val c = {}            //compiler implicitly types c: () -> Unit
    val d: () -> Any = {k=2}  //compiler error (??: lambda returns Unit, Unit is derived from Any)
    val e: () -> Unit = { k=2 } //no compiler error
    val f: () -> Any = e // no compiler error: ?? inconsistent

    fun blah2(v: () -> Any): Any = v()

    companion object{
        @JvmStatic
        fun main(args: Array<String>) {
            val tn = TestLambdaReturnType()
            tn.a = tn.b

            tn.blah2(tn.a) //ni compiler error, no surprise
            tn.blah2(tn.b) //no compiler error, no surprise
            tn.blah2(tn.c) //no compiler error, no surprise
            tn.blah2 { tn.k=2 }  //error: why "f" different than "v"?
        }
    }
}

The lambda must have a return type. In these examples, the return type is Unit. But based upon these examples, there appears to be two types of Unit, one that derives from Any and one that derives from who-knows-what.

like image 24
Les Avatar answered Dec 11 '25 10:12

Les