You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -329,6 +329,41 @@ The using clause in class `SortedSet` provides an implementation for the deferre
329
329
**Alternative:** It was suggested that we use a modifier for a deferred given instead of a `= deferred`. Something like `deferred given C[T]`. But a modifier does not suggest the concept that a deferred given will be implemented automatically in subclasses unless an explicit definition is written. In a sense, we can see `= deferred` as the invocation of a magic macro that is provided by the compiler. So from a user's point of view a given with `deferred` right hand side is not abstract.
330
330
It is a concrete definition where the compiler will provide the correct implementation.
331
331
332
+
### Abolish Abstract Givens
333
+
334
+
With `deferred` givens there is no need anymore to also define abstract givens. The two mechanisms are very similar, but the user experience for
335
+
deferred givens is generally more ergonomic. Abstract givens also are uncomfortably close to concrete class instances. Their syntax clashes
336
+
with the quite common case where we want to establish a given without any nested definitions. For instance, consider a given that constructs a type tag:
337
+
```scala
338
+
classTag[T]
339
+
```
340
+
Then this works:
341
+
```scala
342
+
givenTag[String]()
343
+
givenTag[String] with {}
344
+
```
345
+
But the following more natural syntax fails:
346
+
```scala
347
+
givenTag[String]
348
+
```
349
+
The last line gives a rather cryptic error:
350
+
```
351
+
1 |given Tag[String]
352
+
| ^
353
+
| anonymous given cannot be abstract
354
+
```
355
+
The underlying problem is that abstract givens are very rare (and should become completely unnecessary once deferred givens are introduced), yet occupy a syntax that looks very close to the more common case of concrete
356
+
typeclasses without nested definitions.
357
+
358
+
**Proposal:** In the future, let the `= deferred` mechanism be the only way to deliver the functionality of abstract givens. Deprecate the current version of abstract givens, and remove them in a future Scala version.
359
+
360
+
**Benefits:**
361
+
362
+
- Simplification of the language since a feature is dropped
363
+
- Eliminate non-obvious and misleading syntax.
364
+
365
+
The only downside is that deferred givens are restricted to be used in traits, whereas abstract givens are also allowed in abstract classes. But I would be surprised if actual code relied on that difference, and such code could in any case be easily rewritten to accommodate the restriction.
366
+
332
367
## New Given Syntax
333
368
334
369
A good language syntax is like a Bach fugue: A small set of motifs is combined in a multitude of harmonic ways. Dissonances and irregularities should be avoided.
@@ -340,106 +375,185 @@ given [A](using Ord[A]): Ord[List[A]] with
340
375
```
341
376
The `:` feels utterly foreign in this position. It's definitely not a type ascription, so what is its role? Just as bad is the trailing `with`. Everywhere else we use braces or trailing `:` to start a scope of nested definitions, so the need of `with` sticks out like a sore thumb.
342
377
343
-
We arrived at that syntax not because of a flight of fancy but because even after trying for about a year to find other solutions it seemed like the least bad alternative. The awkwardness of the given syntax arose because we insisted that givens could be named or anonymous, with the default on anonymous, that we would not use underscore for an anonymous given, and that the name, if present, had to come first, and have the form `name [parameters] :`. In retrospect, that last requirement showed a lack of creativity on our part.
344
-
345
378
Sometimes unconventional syntax grows on you and becomes natural after a while. But here it was unfortunately the opposite. The longer I used given definitions in this style the more awkward they felt, in particular since the rest of the language seemed so much better put together by comparison. And I believe many others agree with me on this. Since the current syntax is unnatural and esoteric, this means it's difficult to discover and very foreign even after that. This makes it much harder to learn and apply givens than it need be.
346
379
347
-
Things become much simpler if we introduce the optional name instead with an `as name` clause at the end, just like we did for context bounds. We can then use a more intuitive syntax for givens like this:
380
+
The previous conditional given syntax was inspired from method definitions. If we add the optional name to the previous example, we obtain something akin to an implicit method in Scala 2:
348
381
```scala
349
-
givenString is Ord:
350
-
defcompare(x: String, y: String) = ...
351
-
352
-
given [A:Ord] =>List[A] is Ord:
382
+
givenlistOrd[A](usingOrd[A]):Ord[List[A]] with
353
383
defcompare(x: List[A], y: List[A]) = ...
354
-
355
-
givenInt is Monoid:
356
-
extension (x: Int) defcombine(y: Int) = x + y
357
-
defunit=0
358
384
```
359
-
Here, the second given can be read as if `A` is an `Ord` then `List[A]` is also an`Ord`. Or: for all `A: Ord`, `List[A]` is `Ord`. The arrow can be seen as an implication, note also the analogy to pattern matching syntax.
385
+
The anonymous syntax was then obtained by simply dropping the name.
386
+
But without a name, the syntax looks weird and inconsistent.
387
+
388
+
This is a problem since at least for typeclasses, anonymous givens should be the norm.
389
+
Givens are like extends clauses. We state a _fact_, that a
390
+
type implements a type class, or that a value can be used implicitly. We don't need a name for that fact. It's analogous to extends clauses, where we state that a class is a subclass of some other class or trait. We would not think it useful to name an extends clause, it's simply a fact that is stated.
391
+
It's also telling that every other language that defines type classes uses anonymous syntax. Somehow, nobody ever found it necessary to name these instances.
360
392
361
-
If explicit names are desired, we add them with `as` clauses:
393
+
A more intuitive and in my opinion cleaner alternative is to decree that a given should always look like it _implements a type_. Conditional givens should look like they implement function types. The `Ord` typeclass instances for `Int` and `List` would then look like this:
362
394
```scala
363
-
givenString is OrdasintOrd:
395
+
givenOrd[String]:
364
396
defcompare(x: String, y: String) = ...
365
397
366
-
given [A:Ord] =>List[A] is OrdaslistOrd:
398
+
given [A:Ord] =>Ord[List[A]]:
367
399
defcompare(x: List[A], y: List[A]) = ...
368
-
369
-
givenInt is MonoidasintMonoid:
370
-
extension (x: Int) defcombine(y: Int) = x + y
371
-
defunit=0
372
400
```
401
+
The second, conditional instance looks like it implements the function type
402
+
```scala
403
+
[A:Ord] =>Ord[List[A]]
404
+
```
405
+
Another way to see this is as an implication:
406
+
If `A` is a type that is `Ord`, then `List[A]` is `Ord` (and the rest of the given clause gives the implementation that makes it so).
407
+
Equivalently, `A` is `Ord`_implies_`List[A]` is `Ord`, hence the `=>`.
373
408
374
-
The underlying principles are:
409
+
Yet another related meaning is that the given clause establishes a _context function_ of type `[A: Ord] ?=> Ord[List[A]]` that is automatically applied to evidence arguments of type `Ord[A]` and that yields instances of type `Ord[List[A]]`. Since givens are in any case applied automatically to all their arguments, we don't need to specify that separately with `?=>`, a simple `=>` arrow is sufficiently clear and is easier to read.
375
410
376
-
- A `given` clause consists of the following elements:
411
+
All these viewpoints are equivalent, in a deep sense. This is exactly the Curry Howard isomorphism, which equates function types and implications.
377
412
378
-
- An optional _precondition_, which introduces type parameters and/or using clauses and which ends in `=>`,
379
-
- the implemented _type_,
380
-
- an optional name binding using `as`,
381
-
- an implementation which consists of either an `=` and an expression,
382
-
or a template body.
413
+
In the new syntax, a `given` clause consists of the following elements:
383
414
384
-
- Since there is no longer a middle `:` separating name and parameters from the implemented type, we can use a `:` to start the class body without looking unnatural, as is done everywhere else. That eliminates the special case where `with` was used before.
415
+
- An optional name binding `id :`
416
+
- Zero or more _conditions_, which introduce type or value parameters. Each precondition ends in a `=>`.
417
+
- the implemented _type_,
418
+
- an implementation which consists of either an `=` and an expression,
419
+
or a template body.
385
420
386
-
This will be a fairly significant change to the given syntax. I believe there's still a possibility to do this. Not so much code has migrated to new style givens yet, and code that was written can be changed fairly easily. Specifically, there are about a 900K definitions of `implicit def`s
387
-
in Scala code on Github and about 10K definitions of `given ... with`. So about 1% of all code uses the Scala 3 syntax, which would have to be changed again.
421
+
**Examples:**
388
422
389
-
Changing something introduced just recently in Scala 3 is not fun,
390
-
but I believe these adjustments are preferable to let bad syntax
391
-
sit there and fester. The cost of changing should be amortized by improved developer experience over time, and better syntax would also help in migrating Scala 2 style implicits to Scala 3. But we should do it quickly before a lot more code
392
-
starts migrating.
423
+
Here is an enumeration of common forms of given definitions in the new syntax. We show the following use cases:
393
424
394
-
Migration to the new syntax is straightforward, and can be supported by automatic rewrites. For a transition period we can support both the old and the new syntax. It would be a good idea to backport the new given syntax to the LTS version of Scala so that code written in this version can already use it. The current LTS would then support old and new-style givens indefinitely, whereas new Scala 3.x versions would phase out the old syntax over time.
425
+
1. A simple typeclass instance, such as `Ord[Int]`.
426
+
2. A parameterized type class instance, such as `Ord` for lists.
427
+
3. A type class instance with an explicit context parameter.
428
+
4. A type class instance with a named eexplicit context parameter.
429
+
4. A simple given alias.
430
+
5. A parameterized given alias
431
+
6. A given alias with an explicit context parameter.
432
+
8. An abstract or deferred given
433
+
9. A by-name given, e.g. if we have a given alias of a mutable variable, and we
434
+
want to make sure that it gets re-evaluated on each access.
435
+
```scala
436
+
// Simple typeclass
437
+
givenOrd[Int]:
438
+
defcompare(x: Int, y: Int) = ...
395
439
440
+
// Parameterized typeclass with context bound
441
+
given [A:Ord] =>Ord[List[A]]:
442
+
defcompare(x: List[A], y: List[A]) = ...
396
443
397
-
### Abolish Abstract Givens
444
+
// Parameterized typeclass with context parameter
445
+
given [A] =>Ord[A] =>Ord[List[A]]:
446
+
defcompare(x: List[A], y: List[A]) = ...
398
447
399
-
Another simplification is possible. So far we have special syntax for abstract givens:
400
-
```scala
401
-
givenx:T
448
+
// Parameterized typeclass with named context parameter
449
+
given [A] => (ord: Ord[A]) =>Ord[List[A]]:
450
+
defcompare(x: List[A], y: List[A]) = ...
451
+
452
+
// Simple alias
453
+
givenOrd[Int] =IntOrd()
454
+
455
+
// Parameterized alias with context bound
456
+
given [A:Ord] =>Ord[List[A]] =
457
+
ListOrd[A]
458
+
459
+
// Parameterized alias with context parameter
460
+
given [A] =>Ord[A] =>Ord[List[A]] =
461
+
ListOrd[A]
462
+
463
+
// Abstract or deferred given
464
+
givenContext= deferred
465
+
466
+
// By-name given
467
+
given () =>Context= curCtx
402
468
```
403
-
The problem is that this syntax clashes with the quite common case where we want to establish a given without any nested definitions. For instance
404
-
consider a given that constructs a type tag:
469
+
Here are the same examples, with optional names provided:
405
470
```scala
406
-
classTag[T]
471
+
// Simple typeclass
472
+
givenintOrd:Ord[Int]:
473
+
defcompare(x: Int, y: Int) = ...
474
+
475
+
// Parameterized typeclass with context bound
476
+
givenlistOrd: [A:Ord] =>Ord[List[A]]:
477
+
defcompare(x: List[A], y: List[A]) = ...
478
+
479
+
// Parameterized typeclass with context parameter
480
+
givenlistOrd: [A] =>Ord[A] =>Ord[List[A]]:
481
+
defcompare(x: List[A], y: List[A]) = ...
482
+
483
+
// Parameterized typeclass with named context parameter
We sometimes find it necessary that a given alias is re-evaluated each time it is called. For instance, say we have a mutable variable `curCtx` and we want to define a given that returns the current value of that variable. A normal given alias will not do since by default given aliases are mapped to
508
+
lazy vals.
509
+
510
+
In general, we want to avoid re-evaluation of the given. But there are situations like the one above where we want to specify _by-name_ evaluation instead. The proposed new syntax for this is shown in the last clause above. This is arguably the a natural way to express by-name givens. We want to use a conditional given, since these map to methods, but the set of preconditions is empty, hence the `()` parameter. Equivalently, under the context function viewpoint, we are defining a context function of the form `() ?=> T`, and these are equivalent to by-name parameters.
511
+
512
+
Compare with the current best way to do achieve this, which is to use a dummy type parameter.
409
513
```scala
410
-
givenTag[String]()
411
-
givenTag[String] with {}
514
+
given [DummySoThatItsByName]:Context= curCtx
412
515
```
413
-
But the following more natural syntax fails:
516
+
This has the same effect, but feels more like a hack than a clean solution.
517
+
518
+
**Dropping `with`**
519
+
520
+
In the new syntax, all typeclass instances introduce definitions like normal
521
+
class bodies, enclosed in braces `{...}` or following a `:`. The irregular
522
+
requirement to use `with` is dropped. In retrospect, the main reason to introduce `with` was since a definition like
523
+
414
524
```scala
415
-
givenTag[String]
416
-
```
417
-
The last line gives a rather cryptic error:
525
+
given [A](usingOrd[A]):Ord[List[A]]:
526
+
defcompare(x: List[A], y: List[A]) = ...
418
527
```
419
-
1 |given Tag[String]
420
-
| ^
421
-
| anonymous given cannot be abstract
528
+
was deemed to be too cryptic, with the double meaning of colons. But since that syntax is gone, we don't need `with` anymore. There's still a double meaning of colons, e.g. in
529
+
```scala
530
+
givenintOrd:Ord[Int]:
531
+
...
422
532
```
423
-
The problem is that the compiler thinks that the last given is intended to be abstract, and complains since abstract givens need to be named. This is another annoying dissonance. Nowhere else in Scala's syntax does adding a
424
-
`()` argument to a class cause a drastic change in meaning. And it's also a violation of the principle that it should be possible to define all givens without providing names for them.
533
+
but since now both uses of `:` are very familiar (type ascription _vs_ start of nested definitions), it's manageable. Besides, the problem occurs only for named typeclass instances, which should be the exceptional case anyway.
425
534
426
-
Fortunately, abstract givens are no longer necessary since they are superseded by the new `deferred` scheme. So we can deprecate that syntax over time. Abstract givens are a highly specialized mechanism with a so far non-obvious syntax. We have seen that this syntax clashes with reasonable expectations of Scala programmers. My estimate is that maybe a dozen people world-wide have used abstract givens in anger so far.
427
535
428
-
**Proposal** In the future, let the `= deferred` mechanism be the only way to deliver the functionality of abstract givens.
536
+
**Possible ambiguities**
429
537
430
-
This is less of a disruption than it might appear at first:
538
+
If one wants to define a given for an a actual function type (which is probably not advisable in practice), one needs to enclose the function type in parentheses, i.e. `given ([A] => F[A])`. This is true in the currently implemented syntax and stays true for all discussed change proposals.
431
539
432
-
-`given T` was illegal before since abstract givens could not be anonymous.
433
-
It now means a concrete given of class `T` with no member definitions.
434
-
-`given x: T` is legacy syntax for an abstract given.
435
-
-`given T as x = deferred` is the analogous new syntax, which is more powerful since
436
-
it allows for automatic instantiation.
437
-
-`given T = deferred` is the anonymous version in the new syntax, which was not expressible before.
540
+
The double meaning of : with optional prefix names is resolved as usual. A : at the end of a line starts a nested definition block. If for some obscure reason one wants to define a named given on multiple lines, one has to format it as follows:
541
+
```scala
542
+
givenintOrd
543
+
:Ord= ...
544
+
```
438
545
439
-
**Benefits:**
546
+
**Summary**
440
547
441
-
- Simplification of the language since a feature is dropped
442
-
- Eliminate non-obvious and misleading syntax.
548
+
This will be a fairly significant change to the given syntax. I believe there's still a possibility to do this. Not so much code has migrated to new style givens yet, and code that was written can be changed fairly easily. Specifically, there are about a 900K definitions of `implicit def`s
549
+
in Scala code on Github and about 10K definitions of `given ... with`. So about 1% of all code uses the Scala 3 syntax, which would have to be changed again.
550
+
551
+
Changing something introduced just recently in Scala 3 is not fun,
552
+
but I believe these adjustments are preferable to let bad syntax
553
+
sit there and fester. The cost of changing should be amortized by improved developer experience over time, and better syntax would also help in migrating Scala 2 style implicits to Scala 3. But we should do it quickly before a lot more code
554
+
starts migrating.
555
+
556
+
Migration to the new syntax is straightforward, and can be supported by automatic rewrites. For a transition period we can support both the old and the new syntax. It would be a good idea to backport the new given syntax to the LTS version of Scala so that code written in this version can already use it. The current LTS would then support old and new-style givens indefinitely, whereas new Scala 3.x versions would phase out the old syntax over time.
443
557
444
558
445
559
### Bonus: Fixing Singleton
@@ -586,7 +700,7 @@ Here are some standard type classes, which were mostly already introduced at the
0 commit comments