22

I have following functions:

fun <T, U> process(t: T, call: (U) -> Unit, map: (T) -> U) = call(map(t))

fun <T> processEmpty(t: T, call: () -> Unit) = process(t, call, {}) // error

but the processEmpty is not compiling. The error message is Type mismatch: inferred type is () -> kotlin.Unit but (kotlin.Unit) -> kotlin.Unit was expected. But if I change this function to

fun <T> processEmpty2(t: T, call: (Unit) -> Unit) = process(t, call, {}) // OK

So what's the difference between () -> Unit and (Unit) -> Unit types? Why first version of processEmpty isn't compiling?

netimen
  • 4,199
  • 6
  • 41
  • 65
  • I found, I think, better way to define `processEmpty` function: `fun processEmpty(t: T, call: () -> Unit) = process(t, {call()}, {}) ` – netimen Feb 12 '16 at 12:01
  • Yes, `{ call() }` is a correct function of one argument (implicit `it`), thus it can be passed as `(Unit) -> Unit`. – hotkey Feb 12 '16 at 12:10

1 Answers1

46

Unit is actually a type that has exactly one value (the value is Unit itself; also, this is why it is named Unit). It corresponds to void in Java, but it's not the same.

Kotlin compiler treats functions with no declared return value as Unit-returning functions, and return Unit can also be omitted. This is why { } is a Unit-returning function.

But this is not applied to arguments. To be strict, when you declare a function with Unit argument or (Unit) -> Unit function variable, you have to pass an argument of type Unit at call site. The only value to pass is Unit.

A lambda with no specified arguments like { doSomething() } is treated both as a function with no arguments and as a function with single implicit argument it. You can use { } both as () -> Unit and (Unit) -> Unit.

As to the call site, as said above, Unit has to be passed:

val f: (Unit) -> Unit = { println("Hello") }

f(Unit) // the only valid call

Whereas () -> Unit functions do not need an argument to be passed:

val f: () -> Unit = { println("Hello") }

f() // valid call


In your example, type inference happens as follows:
fun <T, U> process(t: T, call: (U) -> Unit, map: (T) -> U) = call(map(t))

fun <T> processEmpty(t: T, call: () -> Unit) = process(t, call, {}) // error
  1. map: (T) -> U = { }, thus a replacement for U is Unit returned from { }.
  2. Therefore call should be (Unit) -> Unit.
  3. call: () -> Unit which is not the same to (Unit) -> Unit, as stated above. Error.
hotkey
  • 140,743
  • 39
  • 371
  • 326