|
| 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. |
0 commit comments