Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert function output to Unit with Kotlin

I have troubles with a function in Kotlin that should return Unit, but due to a usage of another function returning a Boolean, there is a type mismatch.

Here is a contrived example:

fun printAndReturnTrue(bar: Int): Boolean {
    println(bar)
    return true
}

fun foo(bar: Int): Unit = when(bar) {
    0 -> println("0")
    else -> printAndReturnTrue(bar)
}

Here, I actually do not care about the fact that printAndReturnTrue returns a boolean. I just want foo to perform side-effect operations. But the compiler warns about a type mismatch: my else should return a Unit value.

Is there a nice way to convert a value to Unit?

The simplest solutions I see are:

fun foo(bar: Int): Unit = when(bar) {
    0 -> println("0")
    else -> { 
        printAndReturnTrue(bar)
        Unit
    }
}

or:

fun foo(bar: Int): Unit = when(bar) {
    0 -> println("0")
    else -> eraseReturnValue(printAndReturnTrue(bar))
}

fun eraseReturnValue(value: Any) = Unit

Or I use the full function form:

fun foo(bar: Int): Unit { 
    when(bar) {
        0 -> println("0")
        else -> printAndReturnTrue(bar)
    }
}

I am sure there are some idiomatic ways to do that (or is is the last example?), but for now I did not find them.

like image 324
Vincent Hiribarren Avatar asked Nov 18 '17 19:11

Vincent Hiribarren


3 Answers

Unfortunaly, there's no idiomatic way to do this. A similiar problem, passing a lambda of type (T) -> Boolean to a function accepting (T) -> Unit lambdas has come up before, and the auto conversion required there is supposed to come to the language at some future point.

For now, you could use an extension function at the end of the when expression to coerce it back into a Unit value:

fun Any?.toUnit() = Unit

fun foo(bar: Int): Unit = when(bar) {
    0 -> println("0")
    else -> printAndReturnTrue(bar)
}.toUnit()

Alternatively, an extension property, if you happen to like that better:

val Any?.unit get() = Unit

fun foo(bar: Int): Unit = when(bar) {
    0 -> println("0")
    else -> printAndReturnTrue(bar)
}.unit

Of course you can omit the explicit Unit return type of the function if you use either of these.

like image 105
zsmb13 Avatar answered Sep 29 '22 12:09

zsmb13


I think the last is most idiomatic. Though you don't need explicit : Unit, it's the default for block form if no return type is specified and if you try to return something else you'll get an error.

But note a subtle detail: when when is used as an expression, else is required unless the compiler can prove all cases are handled; in the block form it's "used as a statement" and unhandled cases are ignored.

like image 44
Alexey Romanov Avatar answered Sep 29 '22 13:09

Alexey Romanov


As another alternative, you could make a higher order function that swallows the output of the function returning a value:

fun consume (fn: () -> Any): Unit {
  fn()
}

Giving:

fun foo(bar: Int): Unit = when(bar) {
    0 -> println("0")
    else -> consume { printAndReturnTrue(bar) }
}
like image 25
Tormod Haugene Avatar answered Sep 29 '22 11:09

Tormod Haugene