Home > front end >  Confusing function type usage in Kotlin
Confusing function type usage in Kotlin

Time:02-16

I'm reading the kotlin coroutine source code.

see the code snaps below:

  public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
        when (this) {
            DEFAULT -> block.startCoroutineCancellable(receiver, completion)
            ATOMIC -> block.startCoroutine(receiver, completion)
            UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
            LAZY -> Unit // will start lazily
        }

in CoroutineStart.kt

and

  internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
    receiver: R, completion: Continuation<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
) =
    runSafely(completion) {
        createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
    }

In Cancellable.kt

What my confusion is:

block as a function which type is R.() -> T, how can it invoke startCoroutineCancellable which function type of (R) -> T

CodePudding user response:

R.() -> T and (R) -> T are very similar function types from the caller perspective. They just receive R and return T. They differ mostly from the perspective of their body. See this example:

val f1: Int.() -> String = { toString() }
val f2: (Int) -> String = f1
val f3: Int.() -> String = f2

As we can see, we can convert between both types freely, even without an explicit cast.

CodePudding user response:

This is explained in the "Function Types" section of the language spec (emphasis mine).

A function type with receive FTR

FTR(RT, A1, A2, ... An) -> R

[...]

From the type system’s point of view, it is equivalent to the following function type

FTR(RT, A1, A2, ... An) -> R ≡ FT(RT, A1, A2, ... An) -> R

i.e. receiver is considered as yet another argument of its function type.

[...] for example, these two types are equivalent w.r.t. type system

Int.(Int) -> String
(Int, Int) -> String

So an instance of R.() -> T can totally be used as the receiver of an extension on (R) -> T and vice versa, because the type system sees them as the same thing.

Do note that these two types are not the same in the context of overload resolution. For example, if you say someInt.bar(), and there are two bars in scope, one with type Int.() -> Unit and the other with type (Int) -> Unit, then overload resolution will know that these are functions with different types and pick the former one.

Side note: the function types used in startCoroutineCancellable and invoke are technically suspending function types, but what is said above still applies.

  • Related