File: multi-declarations-in-parameters.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 (240 lines) | stat: -rw-r--r-- 8,080 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
# Multi-declarations in Parameters

* **Timeframe**: M10
* **Why**: Breaking changes in syntactic structures (lambdas)

## Goal

Support multi-declarations in parameters of lambdas, functions, constructors and setters.

## Examples

``` kotlin
// decomposing pairs in a lambda
listOfPairs.map {
  (a, b) -> a + b
}

// decompose a parameter:
fun foo((a, b): Pair<Int, String>, c: Bar)
// can be called as
foo(pair, bar)

// decompose a constructor parameter
class C(val (a, b): Pair<Int, String>) {}
```

## TODO

- names of decomposed parameters in Kotlin, e.g. for named arguments

## Syntax

Old lambda syntax:

``` kotlin
{ a, b -> ... } // two parameters
{ (a, b) -> ... } // two parameters
{ (a: Int, b: String) -> ... } // parameter types
{ (a, b): Int -> ... } // return type
{ T.(a, b): Int -> ... } // receiver type
```

New syntax:

Common short form:
``` kotlin
{ a -> ... } // one parameter
{ a, b -> ... } // two parameters
{ (a, b) -> ... } // a decomposed pair
{ (a, b), c -> ... } // a decomposed pair and another parameter
{ ((a, b), c) -> ... } // ??? a decomposed pair whose first component is a pair
```

No return type nor receiver type in the short form:
``` kotlin
{ a: A -> ... } // one parameter
{ a, b: B -> ... } // two parameters
{ (a, b: B) -> ... } // a decomposed pair
{ (a, b): Pair<A, B> -> ... } // a decomposed pair
{ (a, b: B), c -> ... } // a decomposed pair and another parameter
{ ((a, b), c: C) -> ... } // ??? a decomposed pair whose first component is a pair
```

> (BAD OPTION) To disambiguate, we could demand a prefix:
``` kotlin
{ fun Recv.(((a: A, b: B): Pair<A, B>, c: C): Pair<Pair<A, B>, C>): R -> ... } // ??? a decomposed pair whose first component is a pair
{ fun (((a: A, b: B): Pair<A, B>, c: C): Pair<Pair<A, B>, C>): R -> ... } // ??? a decomposed pair whose first component is a pair
{ fun ( ((a, b), c) ): R -> ... } // ??? a decomposed pair whose first component is a pair
{ fun (((a, b), c: C)) -> ... } // ??? a decomposed pair whose first component is a pair
{ fun (((a, b), c): Pair<Pair<A, B>, C>) -> ... } // ??? a decomposed pair whose first component is a pair
```
Rather hairy.

But we have this form coming (needed for local returns):
``` kotlin
foo(fun(): R {
    return r // local return
})
```

We can't omit return type in this form. But we use it only when we need return type/receiver type:
``` kotlin
fun Recv.(((a: A, b: B): Pair<A, B>, c: C): Pair<Pair<A, B>, C>): R { ... } // a decomposed pair whose first component is a pair
fun (((a: A, b: B): Pair<A, B>, c: C): Pair<Pair<A, B>, C>): R { ... } // a decomposed pair whose first component is a pair
fun ( ((a, b), c) ): R { ... } // ??? a decomposed pair whose first component is a pair
fun (((a, b), c: C)): R { ... } // ??? a decomposed pair whose first component is a pair
fun (((a, b), c): Pair<Pair<A, B>, C>): R { ... } // ??? a decomposed pair whose first component is a pair
fun (a) {} // return type is Unit
```
Difference from normal functions: we can omit parameter types, we can omit the name (don't have to).
Difference from lambdas: can specify return type and receiver type + returns are local.

### Quick summary of syntactic changes

- functions
  - allow multi-declarations in parameters
  - allow functions as expressions (anonymous or named)
- lambdas
  - allow multi-declarations in lambda parameters
  - no return type/receiver type in lambda parameters, use anonymous function instead

### Grammar

TODO

## PSI changes

Create a common superclass for lambdas and anonymous functions, most clients shouldn't notice the change

## Front-end checks and language rules

New concept introduced: "function expression" (looks like a function declaration, but works as an expression) as opposed to "lambda" (both are special cases of "function literal").

### Function call sites

From the outside a multi-declared parameter is seen as one parameter of the specified type:
``` kotlin
foo(pair) // caller can not pass two separate values here
```

No changes to the call-site checking are required.

### Function declaration sites

Function *declarations* are not allowed to omit types of their parameters:
``` kotlin
fun foo((a, b): Pair<A, B>) {...} // type is required
```

Types of individual components of the multi-declarations are optional:
``` kotlin
fun foo((a: A, b: B): Pair<A, B>) {...} // individual types of `a` and `b` are not required
```

Default values are only allowed for whole parameters, not for individual components:
``` kotlin
fun foo((a, b): AB = AB(1, 2)) {...}
```

All names in the parameter list belong to one and the same namespace:
``` kotlin
fun foo((a, b): AB, a: A) // redeclaration: two variables named `a`
```

One can use components of previously declared parameters in default values:
``` kotlin
fun foo((a, b): AB, c: C = C(a, b)) {...}
```

A parameter can be decomposed iff there are appropriate component functions available at the declaration site:
``` kotlin
fun Int.component1() = 1
fun Int.component2() = 1
fun foo((a, b): Int) {...}
```
other wise it's an error.

Component functions must be checked against the declared types of component parameters if they are present:
``` kotlin
fun foo((a: String, b): Int) {...} // error: Int.component1()'s return type is Int, incompatible with String
```

### Function expressions

Function expression syntax differs from function declaration syntax in the following ways:
- function name can be omitted
  - consequently, receiver type can precede the parameter list directly
- type parameters are not allowed
- `where` clause is not allowed
- parameter types can be omitted (even for decomposed parameters)
- parameter default values are not allowed
- varargs are allowed, but useless (warning issued)

NOTE: local returns are allowed in function expressions without qualification.
ISSUE: when a function expression is inlined, unqualified returns must remain local. Wouldn't this confuse the reader?

NOTE: function expression can not be passed to a function call outside the parentheses

### Lambda expressions

In a lambda, only parameters (possibly decomposed) and their types can be specified. There's no way to explicitly specify the return type or receiver type. Those have to be inferred, otherwise function expression must be used.

TODO: support qualified returns in lambdas (when return type is unknown, nad has to be inferrred).

### Nested multi-declarations

Example:
``` kotlin
val (a, (b, c)) = abc // e.g. of type Pair<A, Pair<B, C>>
```

This translates to
```
tmp1 <- abc
a <- tmp1.component1()
tmp2 <- tmp1.component2()
b <- tmp2.component1()
c <- tmp2.component2()
```

If some of the types of `a`, `b` or `c` are specified, then front-end verifies that respective component function results match the expected types.

Biggest issue: type inference for function literals. 

Expected type known entirely:
``` kotlin
fun foo((Pair<A, Pair<B, C>>) -> Unit) {}

foo { (a, (b, c)) -> ... }
```
In this case all we need is check that appropriate component functions are available (and that their types match specifications, if any).

Expected type contains type parameters:
``` kotlin
fun <T> foo(t: T, (T) -> Unit) {...}

foo(ABC) {(a, (b, c)) -> ...}
```
In this case we can't check the component conventions before T is fully resolved to a type.

It seems that this does not impose any significant issues on the inference, and can go right before the normal type checking of the body of a lambda.

## Semantics and Back-end changes

TODO
- what is the Java name of this parameter: `(a, b): Pair<A, B>`?
  - maybe `a_b`
- make components available in default parameter values
- create locals for components, assign values (on nested decompositions, avoid calling the same component twice)
- make sure that function expressions are inlined as well as lambda expressions

## IDE Changes

New intentions:
- Convert lambda <-> anonymous function (mind the returns!)

Affected functionality:
- Change signature
- Move lambda outside/inside parentheses
- Specify types explicitly in a lambda (use conversion to anonymous function)