Skip to content

Commit 6644875

Browse files
committed
Split out abstract and alias witnesses into separate proposal
1 parent b86e91d commit 6644875

File tree

6 files changed

+125
-98
lines changed

6 files changed

+125
-98
lines changed

docs/docs/reference/witnesses/discussion.md

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,22 @@ The witness proposal consists of two main parts:
99

1010
- Define a new [high-level syntax for witnesses](./witnesses.html) that works out better the intent underlying implicit definitions.
1111
- Define a [new syntax for implicit parameters](./witness-params.html) that aligns formal parameters and arguments.
12+
- Define [abstract and alias witnesses](./replacing-implicits.html) and replace all existing usages of `implicit` in the language.
1213

13-
## Other Uses of `implicit`
14-
15-
The only use cases that are not yet covered by the proposal are implicit conversions and implicit classes. We do not propose to use `witness` in place of `implicit` for these, since that would bring back the uncomfortable similarity between implicit conversions and parameterized implicit aliases. However, there is a way to drop implicit conversions entirely. Scala 3 already [defines](https://github.com/lampepfl/dotty/pull/2065) a class `ImplicitConverter` whose instances are available as implicit conversions.
16-
```scala
17-
abstract class ImplicitConverter[-T, +U] extends Function1[T, U]
18-
```
19-
One could define all implicit conversions as witnesses of this class. E.g.
20-
```scala
21-
witness StringToToken for ImplicitConverter[String, Token] {
22-
def apply(str: String): Token = new KeyWord(str)
23-
}
24-
```
25-
The fact that this syntax is more verbose than simple implicit defs could be a welcome side effect since it might dampen any over-enthusiasm for defining implicit conversions.
26-
27-
That leaves implicit classes. Most use cases of implicit classes are probably already covered by extension methods. For the others, one could always fall back to a pair of a regular class and an `ImplicitConverter` witness. It would be good to do a survey to find out how many classes would be affected.
2814

2915
## Migration
3016

3117
New and old syntax would co-exist initially. Rewrite rules could rewrite old syntax to new automatically. This is trivial in the case of implicit parameters and implicit function types. It is a bit more involved in the case of implicit definitions, since more extensive pattern matching is required to recognize a definition that can be rewritten to a witness.
3218

19+
The third part (replacing existing implicits) should be adopted well after the first two parts
20+
are implemented. Alias and abstract witnesses could be introduced together with the other witness definitions, but could also come later.
21+
3322
## Discussion
3423

3524
This is a rather sweeping proposal, which will affect most Scala code. Here are some supporting arguments and a summary of alternatives that were considered.
3625

3726
The witness definition syntax makes the definition of implicit instances clearer and more concise. People have repeatedly asked for specific "typeclass syntax" in Scala. I believe that witnesses together with extension methods address this ask quite well.
3827

39-
A contentious point is whether we want abstract and alias witnesses. As an alternative, would could also keep the current syntax for implicit vals and defs, which can express the same concepts. The main advantage to introduce abstract and alias witnesses is that it would
40-
allow us to drop implicit definitions altogether.
41-
4228
Probably the most far reaching and contentious changes affect implicit parameters. There might be resistance to change, because the existing scheme seems to work "well enough". However, I believe there is a price to pay for the status quo. The need to write `.apply` occasionally to force implicit arguments is already bad. Worse is the fact that implicits always have to come last, which makes useful program patterns much more cumbersome than before and makes the language less regular.
4329

4430
Several alternatives to the proposed syntax changes for implicit parameters were considered:
@@ -55,3 +41,6 @@ Several alternatives to the proposed syntax changes for implicit parameters were
5541
This scheme admits some new patterns, such as an explicit parameter that can be
5642
used as a witness, or an implicitly passed parameter that is not a witness itself.
5743
But the syntax looks unfamiliar and suffers from the choice paradox.
44+
45+
A contentious point is whether we want abstract and alias witnesses. As an alternative, one would could also keep the current syntax for implicit vals and defs, which can express the same concepts. The main advantage to introduce abstract and alias witnesses is that it would
46+
allow us to drop implicit definitions altogether.

docs/docs/reference/witnesses/motivation.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ Can implicit function types help? Implicit function types allow to abstract over
3737

3838
I believe a good name for this concept is _witness_. A term is a witness for a type if it is an implicit instance of this type. It is secondary whether this instance takes the form of a `val` or `object` or whether it is a method. It would be better to have a uniform syntax for all of these kinds of instances.
3939

40-
The next sections elaborate such an alternative design. It consists of two proposals which are independent of each other:
40+
The next sections elaborate such an alternative design. It consists of three proposals:
4141

4242
- A proposal to replace implicit _definitions_ by [witness definitions](./witnesses.html).
4343
- A proposal for a [new syntax](./witness-params.html) of implicit _parameters_ and their _arguments_.
44+
- A proposal to [replace all remaining usages](./replacing-implicits) of `implicit` in the language.
45+
46+
The first two proposals are independent of each other. The last one would work only if the first two are adopted.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
layout: doc-page
3+
title: "Replacing Implicits"
4+
---
5+
6+
The previous two pages proposed high-level syntax for implicit definitions and a new syntax for implicit parameters.
7+
8+
This addresses all the issues mentioned in the [Motivation](./motivation.md), but it leaves us with two related constructs: new style witnesses and context parameters and traditional implicits. This page discusses what would be needed to get rid of `implicit` entirely.
9+
10+
## Abstract and Alias Witnesses
11+
12+
Witness definitions can be abstract.
13+
As an example for an abstract witness consider the following fragment that's derived from Scala's Tasty extractor framework:
14+
```scala
15+
trait TastyAPI {
16+
type Symbol
17+
trait SymDeco {
18+
def name(this sym: Symbol): Name
19+
def tpe(this sym: Symbol): Type
20+
}
21+
witness symDeco: SymDeco
22+
}
23+
```
24+
Here, `symDeco` is available as a witness for the `SymDeco` trait but its actual implementation
25+
is deferred to subclasses of the `TastyAPI` trait.
26+
27+
An example of an alias witness would be an implementation of `symDeco` in terms of some internal compiler structure:
28+
```scala
29+
trait TastyImpl extends TastyAPI {
30+
witness symDeco: SymDeco = compilerSymOps
31+
}
32+
```
33+
Note that the result type of an abstract or alias witness is introduced with a colon instead of a `for`. This seems more natural since it evokes the similarity to implicit parameters, whose type is also given following a `:`. It also avoids the syntactic ambiguity with a witness
34+
for a class that does not add any new definitions. I.e.
35+
```scala
36+
witness a for C // concrete witness for class C, no definitions added
37+
witness b: C // abstract witness for class C
38+
```
39+
Further examples of alias witnesses:
40+
```scala
41+
witness ctx = outer.ctx
42+
witness ctx: Context = outer.ctx
43+
witness byNameCtx(): Context = outer.ctx
44+
witness f[T]: C[T] = new C[T]
45+
witness g with (ctx: Context): D = new D(ctx)
46+
```
47+
As another example, if one had already defined classes `IntOrd` and `ListOrd`, witnesses for them could be defined as follows:
48+
```scala
49+
class IntOrd extends Ord[Int] { ... }
50+
class ListOrd[T: Ord] extends Ord[List[T]] { ... }
51+
52+
witness intOrd: Ord[Int] = new IntOrd
53+
witness listOrd[T: Ord]: Ord[List[T]] = new ListOrd[T]
54+
```
55+
The result type of a alias witness is mandatory unless the witness definition
56+
occurs as a statement in a block and lacks any type or value parameters. This corresponds to the same restriction for implicit vals in Dotty.
57+
58+
Abstract witnesses are equivalent to abstract implicit defs. Alias witnesses are equivalent to implicit defs if they are parameterized or for implicit vals otherwise. For instance, the witnesses defined so far in this section are equivalent to:
59+
```scala
60+
implicit def symDeco: SymDeco
61+
implicit val symDeco: SymDeco = compilerSymOps
62+
63+
implicit val ctx = outer.ctx
64+
implicit val ctx: Context = outer.ctx
65+
implicit def byNameCtx(): Ctx = outer.ctx
66+
implicit def f[T]: C[T] = new C[T]
67+
implicit def g(implicit ctx: Context): D = new D(ctx)
68+
69+
implicit val intOrd: Ord[Int] = new IntOrd
70+
implicit def listOrd(implicit ev: Ord[T]): Ord[List[T]] = new ListOrd[T]
71+
```
72+
The `lazy` modifier is applicable to unparameterized alias witnesses. If present, the resulting implicit val is lazy. For instance,
73+
```scala
74+
lazy witness intOrd2: Ord[Int] = new IntOrd
75+
```
76+
would be equivalent to
77+
```scala
78+
lazy implicit val intOrd2: Ord[Int] = new IntOrd
79+
```
80+
81+
## Implicit Conversions and Classes
82+
83+
The only use cases that are not yet covered by the proposal are implicit conversions and implicit classes. We do not propose to use `witness` in place of `implicit` for these, since that would bring back the uncomfortable similarity between implicit conversions and parameterized implicit aliases. However, there is a way to drop implicit conversions entirely. Scala 3 already [defines](https://github.com/lampepfl/dotty/pull/2065) a class `ImplicitConverter` whose instances are available as implicit conversions.
84+
```scala
85+
abstract class ImplicitConverter[-T, +U] extends Function1[T, U]
86+
```
87+
One can define all implicit conversions as witnesses of this class. E.g.
88+
```scala
89+
witness StringToToken for ImplicitConverter[String, Token] {
90+
def apply(str: String): Token = new KeyWord(str)
91+
}
92+
```
93+
The fact that this syntax is more verbose than simple implicit defs could be a welcome side effect since it might dampen any over-enthusiasm for defining implicit conversions.
94+
95+
That leaves implicit classes. Most use cases of implicit classes are probably already covered by extension methods. For the others, one could always fall back to a pair of a regular class and an `ImplicitConverter` witness. It would be good to do a survey to find out how many classes would be affected.
96+
97+
## Summoning a Witness
98+
99+
Besides `implicit`, there is also `implicitly`, a method defined in `Predef` that computes an implicit value for a given type. Keeping with the "witness" terminology, it seems apt to introduce the name `summon` for this operation. So `summon[T]` summons a witness for `T`, in the same way as `implicitly[T]` did. The definition of `summon` is straightforward:
100+
```scala
101+
def summon[T] with (x: T) = x
102+
```
103+
104+
## Syntax
105+
106+
The syntax changes for this page are summarized as follows:
107+
```
108+
WitnessDef ::= ...
109+
| id WitnessParams ‘:’ Type ‘=’ Expr
110+
| id ‘=’ Expr
111+
```
112+
In addition, the `implicit` modifier is removed together with all [productions]((http://dotty.epfl.ch/docs/internals/syntax.html) that reference it.

docs/docs/reference/witnesses/witness-params.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,6 @@ This scheme is essentially what MacWire does. MacWire was implemented as a macro
7171
I considered for a while an alternative design where the two notions of an implicit parameter (argument gets synthesized vs. parameter is itself available as an implicit value) are separated. This would allow a nicer expression of component assembly which would not require that dependencies are repeated in the witnesses. The most significant downside of the alternative design is that it's likely to induce choice fatigue. In most cases, implicit parameters should be available itself as a witness, so asking for an opt-in each time a parameter is defined
7272
became quickly tiresome.
7373

74-
## Summoning a Witness
75-
76-
The `implicitly` method defined in `Predef` computes an implicit value for a given type. Keeping with the "witness" terminology, it seems apt to introduce the name `summon` for this operation. So `summon[T]` summons a witness for `T`, in the same way as `implicitly[T]` does. The definition of `summon` is straightforward:
77-
```scala
78-
def summon[T] with (x: T) = x
79-
```
80-
8174
## Implicit Function Types and Closures
8275

8376
Implicit function types are expressed using the new reserved operator `|=>`. Examples:

docs/docs/reference/witnesses/witnesses.md

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -123,76 +123,6 @@ witness ListOrd[T] with (_: Ord[T]) for List[Ord[T]] { ... }
123123

124124
**Design note:** An alternative to the underscore syntax would be to allow the `name:` part to be left out entirely. I.e. it would then be `witness ListOrd[T] with (Ord[T]) for ...`. I am not yet sure which is preferable.
125125

126-
## Abstract and Alias Witnesses
127-
128-
Witness definitions can be abstract.
129-
As an example for an abstract witness consider the following fragment that's derived from Scala's Tasty extractor framework:
130-
```scala
131-
trait TastyAPI {
132-
type Symbol
133-
trait SymDeco {
134-
def name(this sym: Symbol): Name
135-
def tpe(this sym: Symbol): Type
136-
}
137-
witness symDeco: SymDeco
138-
}
139-
```
140-
Here, `symDeco` is available as a witness for the `SymDeco` trait but its actual implementation
141-
is deferred to subclasses of the `TastyAPI` trait.
142-
143-
An example of an alias witness would be an implementation of `symDeco` in terms of some internal compiler structure:
144-
```scala
145-
trait TastyImpl extends TastyAPI {
146-
witness symDeco: SymDeco = compilerSymOps
147-
}
148-
```
149-
Note that the result type of an abstract or alias witness is introduced with a colon instead of a `for`. This seems more natural since it evokes the similarity to implicit parameters, whose type is also given following a `:`. It also avoids the syntactic ambiguity with a witness
150-
for a class that does not add any new definitions. I.e.
151-
```scala
152-
witness a for C // concrete witness for class C, no definitions added
153-
witness b: C // abstract witness for class C
154-
```
155-
Further examples of alias witnesses:
156-
```scala
157-
witness ctx = outer.ctx
158-
witness ctx: Context = outer.ctx
159-
witness byNameCtx(): Context = outer.ctx
160-
witness f[T]: C[T] = new C[T]
161-
witness g with (ctx: Context): D = new D(ctx)
162-
```
163-
As another example, if one had already defined classes `IntOrd` and `ListOrd`, witnesses for them could be defined as follows:
164-
```scala
165-
class IntOrd extends Ord[Int] { ... }
166-
class ListOrd[T: Ord] extends Ord[List[T]] { ... }
167-
168-
witness intOrd: Ord[Int] = new IntOrd
169-
witness listOrd[T: Ord]: Ord[List[T]] = new ListOrd[T]
170-
```
171-
The result type of a alias witness is mandatory unless the witness definition
172-
occurs as a statement in a block and lacks any type or value parameters. This corresponds to the same restriction for implicit vals in Dotty.
173-
174-
Abstract witnesses translate to abstract implicit defs. Alias witnesses translate to implicit defs if they are parameterized or to implicit vals otherwise. For instance, the witnesses defined so far in this section translate to:
175-
```scala
176-
implicit def symDeco: SymDeco
177-
implicit val symDeco: SymDeco = compilerSymOps
178-
179-
implicit val ctx = outer.ctx
180-
implicit val ctx: Context = outer.ctx
181-
implicit def byNameCtx(): Ctx = outer.ctx
182-
implicit def f[T]: C[T] = new C[T]
183-
implicit def g(implicit ctx: Context): D = new D(ctx)
184-
185-
implicit val intOrd: Ord[Int] = new IntOrd
186-
implicit def listOrd(implicit ev: Ord[T]): Ord[List[T]] = new ListOrd[T]
187-
```
188-
The `lazy` modifier is applicable to unparameterized alias witnesses. If present, the resulting implicit val is lazy. For instance,
189-
```scala
190-
lazy witness intOrd2: Ord[Int] = new IntOrd
191-
```
192-
would translate to
193-
```scala
194-
lazy implicit val intOrd2: Ord[Int] = new IntOrd
195-
```
196126

197127
## Witnesses as Typeclass Instances
198128

@@ -251,8 +181,6 @@ Here is the new syntax for witness definitions, seen as a delta from the [standa
251181
TmplDef ::= ...
252182
| ‘witness’ WitnessDef
253183
WitnessDef ::= [id] WitnessParams [‘for’ ConstrApps] [TemplateBody]
254-
| id WitnessParams ‘:’ Type ‘=’ Expr
255-
| id ‘=’ Expr
256184
WitnessParams ::= [DefTypeParamClause] {‘with’ ‘(’ [DefParams] ‘)}
257185
```
258186
The identifier `id` can be omitted only if either the `for` part or the template body is present. If the `for` part is missing, the template body must define at least one extension method.

docs/sidebar.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ sidebar:
4646
url: docs/reference/witnesses/witnesses.html
4747
- title: Witness Parameters
4848
url: docs/reference/witnesses/witness-params.html
49+
- title: Replacing Implicits
50+
url: docs/reference/witnesses/replacing-implicits.html
4951
- title: Discussion
5052
url: docs/reference/witnesses/discussion.html
5153
- title: Other New Features

0 commit comments

Comments
 (0)