File: function-types.md

package info (click to toggle)
kotlin 1.3.31%2Bds1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 109,908 kB
  • sloc: java: 454,756; xml: 18,599; javascript: 10,452; sh: 513; python: 97; makefile: 69; ansic: 4
file content (305 lines) | stat: -rw-r--r-- 13,426 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# Function Types in Kotlin on JVM

## Goals

* Get rid of 23 hardwired physical function classes. One of the problems with them is that they should be effectively duplicated in reflection which means a lot of physical classes in kotlin-runtime.jar.
* Make extension functions assignable to normal functions (and vice versa), so that it's possible to do `listOfStrings.map(String::length)`
* Allow functions with more than 23 parameters, theoretically any number of parameters (in practice 255 on JVM).
* At the same time, allow implementing Kotlin functions easily from Java: `new Function2() { ... }` and overriding `invoke` only would be the best.
Enabling SAM conversions on Java 8 would also be terrific.

## Brief solution overview

* Treat extension functions almost like non-extension functions with one extra parameter, allowing to use them almost interchangeably.
* Introduce a physical class `Function` and unlimited number of *fictitious* (synthetic) classes `Function0`, `Function1`, ... in the compiler front-end
* On JVM, introduce `Function0`..`Function22`, which are optimized in a certain way,
and `FunctionN` for functions with 23+ parameters.
When passing a lambda to Kotlin from Java, one will need to implement one of these interfaces.
* Also on JVM (under the hood) add abstract `FunctionImpl` which implements all of `Function0`..`Function22` and `FunctionN`
(throwing exceptions), and which knows its arity.
Kotlin lambdas are translated to subclasses of this abstract class, passing the correct arity to the super constructor.
* Provide a way to get arity of an arbitrary `Function` object (pretty straightforward).
* Hack `is/as Function5` on any numbered function in codegen (and probably `KClass.cast()` in reflection) to check against `Function` and its arity.

## Extension functions

Extension function type `T.(P) -> R` is now just a shorthand for `@ExtensionFunctionType Function2<T, P, R>`.
`kotlin.extension` is a **type annotation** defined in built-ins.
So effectively functions and extension functions now have the same type,
which means that everything which takes a function will work with an extension function and vice versa.

To prevent unpleasant ambiguities, we introduce additional restrictions:
* A value of an extension function type cannot be **called** as a function, and a value of a non-extension
function type cannot be called as an extension. This requires an additional diagnostic which is only fired
when a call is resolved to the `invoke` with the wrong extension-ness.
(Note that this restriction is likely to be lifted, so that extension functions can be called as functions,
but not the other way around.)
* Shape of a function **literal** argument or a function expression must exactly match
the extension-ness of the corresponding parameter. You can't pass an extension function **literal**
or an extension function expression where a function is expected and vice versa.
If you really want to do that, change the shape, assign literal to a variable or use the `as` operator.

So basically you can now safely coerce values between function and extension function types,
but still should invoke them in the format which you specified in their type (with or without `@ExtensionFunctionType`).

With this we'll get rid of classes `ExtensionFunction0`, `ExtensionFunction1`, ...
and the rest of this article will deal only with usual functions.

## Function0, Function1, ... types

The arity of the functional interface that the type checker can create in theory **is not limited** to any number,
but in practice should be limited to 255 on JVM.

These interfaces are named `kotlin.Function0<R>`, `kotlin.Function1<P0, R>`, ..., `kotlin.Function42<P0, P1, ..., P41, R>`, ...
They are *fictitious*, which means they have no sources and no runtime representation.
Type checker creates the corresponding descriptors on demand, IDE creates corresponding source files on demand as well.
Each of them inherits from `kotlin.Function` (described below) and contains only two functions,
both of which should be synthetically produced by the compiler:
* (declaration) `invoke` with no receiver, with the corresponding number of parameters and return type.
* (synthesized) `invoke` with first type parameter as the extension receiver type, and the rest as parameters and return type.

Call resolution should use the annotations on the type of the value the call is performed on
to select the correct `invoke` and to report the diagnostic if the `invoke` is illegal (see the previous block).

On JVM function types are erased to the physical classes defined in package `kotlin.jvm.internal`:
`Function0`, `Function1`, ..., `Function22` and `FunctionN` for 23+ parameters.

## Function interface

There's also an empty interface `kotlin.Function<R>` which is a supertype for all functions.

``` kotlin
package kotlin

interface Function<out R>
```

It's a physical interface, declared in platform-agnostic built-ins, and present in `kotlin-runtime.jar` for example.
However, its declaration is **empty** and should be empty because every physical JVM function class `Function0`, `Function1`, ...
inherits from it (and adds `invoke()`), and we don't want to override anything besides `invoke()` when doing it from Java code.

## Functions with 0..22 parameters at runtime

There are 23 function interfaces in `kotlin.jvm.functions`: `Function0`, `Function1`, ..., `Function22`.
Here's `Function1` declaration, for example:

``` kotlin
package kotlin.jvm.functions

interface Function1<in P1, out R> : kotlin.Function<R> {
    fun invoke(p1: P1): R
}
```

These interfaces are supposed to be inherited from by Java classes when passing lambdas to Kotlin.
They shouldn't be used from Kotlin however, because normally you would use a function type there,
most of the time even without mentioning built-in function classes: `(P1, P2, P3) -> R`.

## Translation of Kotlin lambdas

There's also `FunctionImpl` abstract class at runtime which helps in implementing `arity` and vararg-invocation.
It inherits from all the physical function classes, unfortunately (more on that later).

``` java
package kotlin.jvm.internal;

// This class is implemented in Java because supertypes need to be raw classes
// for reflection to pick up correct generic signatures for inheritors
public abstract class FunctionImpl implements
    Function0, Function1, ..., ..., Function22,
    FunctionN   // See the next section on FunctionN
{
    public abstract int getArity();
    
    @Override
    public Object invoke() {
        // Default implementations of all "invoke"s invoke "invokeVararg"
        // This is needed for KFunctionImpl (see below)
        assert getArity() == 0;
        return invokeVararg();
    }
    
    @Override
    public Object invoke(Object p1) {
        assert getArity() == 1;
        return invokeVararg(p1);
    }
    
    ...
    @Override
    public Object invoke(Object p1, ..., Object p22) { ... }

    @Override    
    public Object invokeVararg(Object... args) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public String toString() {
        // Some calculation involving generic runtime signatures
        ...
    }
}
```

Each lambda is compiled to an anonymous class which inherits from `FunctionImpl` and implements the corresponding `invoke`:

``` kotlin
{ (s: String): Int -> s.length }

// is translated to

object : FunctionImpl(), Function1<String, Int> {
    override fun getArity(): Int = 1

    /* bridge */ fun invoke(p1: Any?): Any? = ...
    override fun invoke(p1: String): Int = p1.length
}
```

## Functions with more than 22 parameters at runtime

To support functions with many parameters there's a special interface in JVM runtime:

``` kotlin
package kotlin.jvm.functions

interface FunctionN<out R> : kotlin.Function<R> {
    val arity: Int
    fun invokeVararg(vararg p: Any?): R
}
```

> TODO: usual hierarchy problems: there are no such members in `kotlin.Function42` (it only has `invoke()`),
> so inheritance from `Function42` will need to be hacked somehow

And another type annotation:

``` kotlin
package kotlin.jvm.functions

annotation class arity(val value: Int)
```

A lambda type with 42 parameters on JVM is translated to `@arity(42) FunctionN`.
A lambda is compiled to an anonymous class which overrides `invokeVararg()` instead of `invoke()`:

``` kotlin
object : FunctionImpl() {
    override fun getArity(): Int = 42

    override fun invokeVararg(vararg p: Any?): Any? { ... /* code */ }
    // TODO: maybe assert that p's size is 42 in the beginning of invokeVararg?
}
```

> Note that `Function0`..`Function22` are provided primarily for **Java interoperability** and as an **optimization** for frequently used functions.
> We can change the number of functions easily from 23 to something else if we want to.
> For example, for `KFunction` this number will be zero,
> since there's no point in implementing a hypothetical `KFunction5` from Java.

So when a large function is passed from Java to Kotlin, the object will need to inherit from `FunctionN`:

``` kotlin
    // Kotlin
    fun fooBar(f: Function42<*,*,...,*>) = f(...)
```

``` java
    // Java
    fooBar(new FunctionN<String>() {
        @Override
        public int getArity() { return 42; }
        
        @Override
        public String invokeVararg(Object... p) { return "42"; }
    }
```

> Note that `@arity(N) FunctionN<R>` coming from Java code will be treated as `(Any?, Any?, ..., Any?) -> R`,
> where the number of parameters is `N`.
> If there's no `@arity` annotation on the type `FunctionN<R>`, it won't be loaded as a function type,
> but rather as just a classifier type with an argument.


## Arity and invocation with vararg

There's an ability to get an arity of a function object and call it with variable number of arguments,
provided by extensions in **platform-agnostic** built-ins.

``` kotlin
package kotlin

@intrinsic val Function<*>.arity: Int
@intrinsic fun <R> Function<R>.invokeVararg(vararg p: Any?): R
```

But they don't have any implementation there.
The reason is, they need **platform-specific** function implementation to work efficiently.
This is the JVM implementation of the `arity` intrinsic (`invokeVararg` is essentially the same):

``` kotlin
fun Function<*>.calculateArity(): Int {
    return if (function is FunctionImpl) {  // This handles the case of lambdas created from Kotlin
        function.arity  // Note the smart cast
    }
    else when (function) {  // This handles all other lambdas, i.e. created from Java
        is Function0 -> 0
        is Function1 -> 1
        ...
        is Function22 -> 22
        is FunctionN -> function.arity  // Note the smart cast
        else -> throw UnsupportedOperationException()  // TODO: maybe do something funny here,
                                                       // e.g. find 'invoke' reflectively
    }
}
```

## `is`/`as`

The newly introduced `FunctionImpl` class inherits from all the `Function0`, `Function1`, ..., `FunctionN`.
This means that `anyLambda is Function2<*, *, *>` will be true for any Kotlin lambda.
To fix this, we need to hack `is` so that it would reach out to the `FunctionImpl` instance and get its arity.

``` kotlin
package kotlin.jvm.internal

// This is the intrinsic implementation
// Calls to this function are generated by codegen on 'is' against a function type
fun isFunctionWithArity(x: Any?, n: Int): Boolean = (x as? Function).arity == n
```

`as` should check if `isFunctionWithArity(instance, arity)`, and checkcast if it is or throw exception if not.

A downside is that `instanceof Function5` obviously won't work correctly from Java. We should provide a public facade to `isFunctionWithArity` which should be used from Java instead of `instanceof`.

Also we should issue warnings on `is Array<Function2<*, *, *>>` (or `as`), since it won't work for empty arrays (there's no instance of `FunctionImpl` to reach out and ask the arity).

## How this will help reflection

`KFunction*` interfaces should be synthesized at compile-time identically to functions.
The compiler should resolve `KFunction{N}` for any `N`, IDEs should synthesize sources when needed,
`is`/`as` should be handled similarly etc.

However, we **won't introduce multitudes of `KFunction`s at runtime**.
The two reasons we did it for `Function`s were Java interop and lambda performance, and they both are not so relevant here.
A great aid was that the contents of each `Function` were trivial and easy to duplicate (23-plicate?),
which is not the case at all for `KFunction`s: they also contain code related to reflection.

So for reflection there will be:
* **fictitious** interfaces `KFunction0`, `KFunction1`, ..., `KFunction42`, ... (defined in `kotlin.reflect`)
* **physical** interface `KFunction` (defined in `kotlin.reflect`)
* **physical** JVM runtime implementation class `KFunctionImpl` (defined in `kotlin.reflect.jvm.internal`)

As an example, `KFunction1` is a fictitious interface (in much the same manner that `Function1` is)
which inherits from `Function1` and `KFunction`. The former lets you call a type-safe `invoke` on a
callable reference, and the latter allows you to use reflection features on the callable reference.

``` kotlin
fun foo(s: String) {}

fun test() {
    ::foo.invoke("")  // ok, calls Function1.invoke
    ::foo.name        // ok, calls KFunction.name
}
```