Skip to content

Commit a33b553

Browse files
committed
Explicit context arguments
1 parent 0d327a6 commit a33b553

File tree

1 file changed

+202
-0
lines changed

1 file changed

+202
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Explicit context arguments
2+
3+
* **Type**: Design proposal
4+
* **Author**: Alejandro Serrano
5+
* **Contributors**: Marat Akhin, Nikita Bobko, Kirill Rakhman
6+
* **Discussion**:
7+
* **Status**: Experimental in 2.3.20
8+
* **Related YouTrack issue**:
9+
10+
## Abstract
11+
12+
We propose an extension to calls to functions with
13+
[context parameters](./KEEP-0367-context-parameters.md),
14+
so that context arguments can be given explicitly.
15+
16+
## Table of contents
17+
18+
* [Abstract](#abstract)
19+
* [Table of contents](#table-of-contents)
20+
* [Motivation](#motivation)
21+
* [Proposal](#proposal)
22+
* [Alternative design choices](#alternative-design-choices)
23+
24+
## Motivation
25+
26+
In the original proposal for [context parameters](./KEEP-0367-context-parameters.md)
27+
the only way to provide the values for context arguments is to do so
28+
**implicitly**, via the new
29+
[context resolution](./KEEP-0367-context-parameters.md#extended-resolution-algorithm)
30+
phase that happens as part of
31+
[overload resolution](https://kotlinlang.org/spec/overload-resolution.html#description).
32+
In practical terms, this means that context arguments either comes from
33+
context parameters declared in the signature, or from a call to `context`
34+
(or a similar function) that introduces new implicit values in the context.
35+
36+
This schema has proven too rigid in practical scenarios. The first issue is
37+
that even giving a single context arguments requires some **nesting**, because
38+
of the call to `context`.
39+
40+
The second issue is the lack of **disambiguation** procedures between different
41+
overloads of the same function. We remark here that the problem is not per se
42+
that we mark calls as ambiguous, but rather the developer has no easy recourse.
43+
44+
Take the following example,
45+
46+
```kotlin
47+
context(theA: A) fun foo() { ... }
48+
context(theB: B) fun foo() { ... }
49+
50+
context(oneA: A, oneB: B) fun bar() {
51+
foo() // resolution ambiguity
52+
}
53+
```
54+
55+
The way to solve the ambiguity in that examples is to create an additional
56+
(useless) function that fixes one of the overloads.
57+
58+
```kotlin
59+
context(oneA: A) fun fooFromA() {
60+
foo() // only the first one is applicable
61+
}
62+
63+
context(oneA: A, oneB: B) fun bar() {
64+
fooFromA()
65+
}
66+
```
67+
68+
However, such a procedure does not scale, and there are situation in which
69+
applying it becomes much harder (for example, if there are intermediate values,
70+
they need to be captured).
71+
72+
## Proposal
73+
74+
We propose to alleviate those problems by introducing **explicit context
75+
arguments**. In short, we can use named parameter syntax to explicitly bind
76+
a value to a context parameter.
77+
78+
```kotlin
79+
context(oneA: A, oneB: B) fun bar() {
80+
foo(theA = oneA)
81+
}
82+
```
83+
84+
This explicit syntax immediately helps with the nesting issue. Furthermore,
85+
the name of the context parameter provides an additional source for
86+
disambiguation.
87+
88+
**Resolution stage.** Explicit context arguments are resolved at the same
89+
time as other named arguments to the function, _not_ during the context
90+
resolution phase. This choice has consequences for type inference and overload
91+
resolution, that may now use additional information from those arguments.
92+
93+
**More specific candidate.** There are no changes to the
94+
[most specific candidate rule](./KEEP-0367-context-parameters.md#extended-resolution-algorithm)
95+
with respect to context arguments. In particular, a function with context
96+
parameters (even if all of them are explicitly given) is considered more
97+
specific than one without.
98+
99+
For example, in the following example,
100+
101+
```kotlin
102+
context(a: A) fun foo() { ... } // (1)
103+
fun foo(a: A) { ... } // (2)
104+
105+
fun test() {
106+
foo(a = A) // resolves to (1)
107+
}
108+
```
109+
110+
Note that you can still disambiguate between each of them: you can use
111+
`context` to be explicit about (1), or you can give the arguments positionally
112+
to choose (2).
113+
114+
**Context parameters naming.**
115+
Context parameters whose name is declared as `_` may not be given as explicit
116+
context argument. We strongly recommend to give names to context parameters,
117+
and make them _unique_ among similar overloads, so the name can be used for
118+
disambiguation purposes.
119+
120+
**Contextual properties.** This proposal does not introduce syntax for
121+
explicitly providing context arguments to contextual properties.
122+
123+
```kotlin
124+
context(a: A) val message: String get() = ...
125+
126+
context(A()) { message } // accepted
127+
message(a = A()) // unresolved
128+
```
129+
130+
We remark that the nesting in this case is required. If we used a property
131+
reference instead, `context(A(), ::message)`, then the context argument is
132+
eagerly resolved as explained in the
133+
[callable references](./KEEP-0367-context-parameters.md#callable-references)
134+
section of the original proposal.
135+
136+
**DSL marker.** Explicit context arguments are _not_ affected by the
137+
applicability restrictions of the `DslMarker` annotation. This aligns with
138+
the behavior for receivers, which also may overcome the restrictions by
139+
explicitly giving them.
140+
141+
**Interaction with [named-only parameters](https://github.com/Kotlin/KEEP/blob/main/proposals/KEEP-0439-named-only-parameters.md).**
142+
Although still in a proposal stage, Kotlin may introduce requirements for
143+
using a parameter only in named form.
144+
145+
```kotlin
146+
fun foo(named a: A) { ... }
147+
```
148+
149+
Alas, this means that the usual disambiguation technique (using positional
150+
arguments) may no longer be used here. There's no proposed solution at this
151+
point, other than recommending library authors to not provide both versions.
152+
153+
## Alternative design choices
154+
155+
Some alternative choices were considered during the design.
156+
157+
**All-or-nothing.** The current proposal allows to given any amount of
158+
explicit context arguments, with the rest of them being resolved in the
159+
usual way. An alternative is to only allow this syntax when all of the
160+
context arguments are given explicitly.
161+
162+
This model, however, makes it difficult to introduce new context parameters
163+
into existing functions. What should be a simple additional `context` wrapper
164+
or context parameter in the signature ends up requiring changes in every
165+
place in which the context arguments are given explicitly.
166+
167+
Being more flexible also means the proposal covers an additional use case,
168+
namely _slightly adjusting_ the context for a function, but without requiring
169+
explicitly repeating all the others.
170+
171+
```kotlin
172+
context(logger: Logger, service: ThingService)
173+
fun doSomething() { ... }
174+
175+
context(logger: Logger, service: ThingService)
176+
fun doSomethingBigger() {
177+
doSomething(logger = OnlyCritical(logger))
178+
// instead of requiring
179+
doSomething(logger = OnlyCritical(logger), service = service)
180+
}
181+
```
182+
183+
Finally, we think that a call in which only some context arguments are given
184+
explicitly is not more difficult to understand than one in which all of them
185+
are explicitly given. The intuitive model (those not explicitly given are
186+
implicitly resolved) is still simple to understand.
187+
188+
**More specific candidate.** We have considered a variation of the rules
189+
for _more specific candidate_ in which a function call in which all context
190+
arguments are explicitly given would count as having no contexts.
191+
192+
```kotlin
193+
context(a: A) fun foo() { ... } // (1)
194+
fun foo(a: A) { ... } // (2)
195+
196+
fun test() {
197+
foo(a = A) // ambiguity
198+
}
199+
```
200+
201+
However, the benefits of such variation are unclear, whereas it introduces
202+
even more complication to a somehow convoluted rule.

0 commit comments

Comments
 (0)