简体   繁体   中英

Confusing function type usage in Kotlin

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

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

ie 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 bar s 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.

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.

A concise and simple way to put it is that a function type defined with a receiver has the same signature as one where that receiver is moved into the parameters as the first parameter. They are interchangeable just about everywhere except when using lambdas to represent them, and when calling invoke on them. You can call a function with receiver with the receiver transposed to first parameter (but not vice versa, calling a receiverless function on an instance of its first parameter).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM