diff --git a/docs/docs/reference/contextual/context-bounds-new.md b/docs/docs/reference/contextual/context-bounds-new.md deleted file mode 100644 index 2ae0e932c7b8..000000000000 --- a/docs/docs/reference/contextual/context-bounds-new.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: doc-page -title: "Context Bounds" ---- - -## Context Bounds - -A context bound is a shorthand for expressing the common pattern of a context parameter that depends on a type parameter. Using a context bound, the `maximum` function of the last section can be written like this: -```scala -def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max) -``` -A bound like `: Ord` on a type parameter `T` of a method or class indicates a context parameter `with Ord[T]`. The context parameter(s) generated from context bounds come last in the definition of the containing method or class. E.g., -```scala -def f[T: C1 : C2, U: C3](x: T)(using y: U, z: V): R -``` -would expand to -```scala -def f[T, U](x: T)(using y: U, z: V)(using C1[T], C2[T], C3[U]): R -``` -Context bounds can be combined with subtype bounds. If both are present, subtype bounds come first, e.g. -```scala -def g[T <: B : C](x: T): R = ... -``` - -### Migration - -To ease migration, context bounds in Dotty map in Scala 3.0 to old-style implicit parameters -for which arguments can be passed either with a `(using ...)` clause or with a normal application. From Scala 3.1 on, they will map to context parameters instead, as is described above. - -If the source version is `3.1` and the `-migration` command-line option is set, any pairing of an evidence -context parameter stemming from a context bound with a normal argument will give a migration -warning. The warning indicates that a `(using ...)` clause is needed instead. The rewrite can be -done automatically under `-rewrite`. - -### Syntax - -``` -TypeParamBounds ::= [SubtypeBounds] {ContextBound} -ContextBound ::= ‘:’ Type -``` diff --git a/docs/docs/reference/contextual/context-bounds.md b/docs/docs/reference/contextual/context-bounds.md index 7f95647c19e7..2ae0e932c7b8 100644 --- a/docs/docs/reference/contextual/context-bounds.md +++ b/docs/docs/reference/contextual/context-bounds.md @@ -3,29 +3,36 @@ layout: doc-page title: "Context Bounds" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./context-bounds-new.html). - ## Context Bounds -A context bound is a shorthand for expressing the common pattern of an implicit parameter that depends on a type parameter. Using a context bound, the `maximum` function of the last section can be written like this: +A context bound is a shorthand for expressing the common pattern of a context parameter that depends on a type parameter. Using a context bound, the `maximum` function of the last section can be written like this: ```scala def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max) ``` -A bound like `: Ord` on a type parameter `T` of a method or class indicates an implicit parameter `(given Ord[T])`. The implicit parameter(s) generated from context bounds come last in the definition of the containing method or class. E.g., +A bound like `: Ord` on a type parameter `T` of a method or class indicates a context parameter `with Ord[T]`. The context parameter(s) generated from context bounds come last in the definition of the containing method or class. E.g., ```scala -def f[T: C1 : C2, U: C3](x: T)(given y: U, z: V): R +def f[T: C1 : C2, U: C3](x: T)(using y: U, z: V): R ``` would expand to ```scala -def f[T, U](x: T)(given y: U, z: V)(given C1[T], C2[T], C3[U]): R +def f[T, U](x: T)(using y: U, z: V)(using C1[T], C2[T], C3[U]): R ``` Context bounds can be combined with subtype bounds. If both are present, subtype bounds come first, e.g. ```scala def g[T <: B : C](x: T): R = ... ``` -## Syntax +### Migration + +To ease migration, context bounds in Dotty map in Scala 3.0 to old-style implicit parameters +for which arguments can be passed either with a `(using ...)` clause or with a normal application. From Scala 3.1 on, they will map to context parameters instead, as is described above. + +If the source version is `3.1` and the `-migration` command-line option is set, any pairing of an evidence +context parameter stemming from a context bound with a normal argument will give a migration +warning. The warning indicates that a `(using ...)` clause is needed instead. The rewrite can be +done automatically under `-rewrite`. + +### Syntax ``` TypeParamBounds ::= [SubtypeBounds] {ContextBound} diff --git a/docs/docs/reference/contextual/conversions-new.md b/docs/docs/reference/contextual/conversions-new.md deleted file mode 100644 index 7faeb3aac0d5..000000000000 --- a/docs/docs/reference/contextual/conversions-new.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -layout: doc-page -title: "Implicit Conversions" ---- - -Implicit conversions are defined by given instances of the `scala.Conversion` class. -This class is defined in package `scala` as follows: -```scala -abstract class Conversion[-T, +U] extends (T => U) -``` -For example, here is an implicit conversion from `String` to `Token`: -```scala -given Conversion[String, Token] { - def apply(str: String): Token = new KeyWord(str) -} -``` -Using an alias this can be expressed more concisely as: -```scala -given Conversion[String, Token] = new KeyWord(_) -``` -An implicit conversion is applied automatically by the compiler in three situations: - -1. If an expression `e` has type `T`, and `T` does not conform to the expression's expected type `S`. -2. In a selection `e.m` with `e` of type `T`, but `T` defines no member `m`. -3. In an application `e.m(args)` with `e` of type `T`, if `T` does define - some member(s) named `m`, but none of these members can be applied to the arguments `args`. - -In the first case, the compiler looks for a given `scala.Conversion` instance that maps -an argument of type `T` to type `S`. In the second and third -case, it looks for a given `scala.Conversion` instance that maps an argument of type `T` -to a type that defines a member `m` which can be applied to `args` if present. -If such an instance `C` is found, the expression `e` is replaced by `C.apply(e)`. - -## Examples - -1. The `Predef` package contains "auto-boxing" conversions that map -primitive number types to subclasses of `java.lang.Number`. For instance, the -conversion from `Int` to `java.lang.Integer` can be defined as follows: -```scala -given int2Integer as Conversion[Int, java.lang.Integer] = - java.lang.Integer.valueOf(_) -``` - -2. The "magnet" pattern is sometimes used to express many variants of a method. Instead of defining overloaded versions of the method, one can also let the method take one or more arguments of specially defined "magnet" types, into which various argument types can be converted. E.g. -```scala -object Completions { - - // The argument "magnet" type - enum CompletionArg { - case Error(s: String) - case Response(f: Future[HttpResponse]) - case Status(code: Future[StatusCode]) - } - object CompletionArg { - - // conversions defining the possible arguments to pass to `complete` - // these always come with CompletionArg - // They can be invoked explicitly, e.g. - // - // CompletionArg.fromStatusCode(statusCode) - - given fromString as Conversion[String, CompletionArg] = Error(_) - given fromFuture as Conversion[Future[HttpResponse], CompletionArg] = Response(_) - given fromStatusCode as Conversion[Future[StatusCode], CompletionArg] = Status(_) - } - import CompletionArg._ - - def complete[T](arg: CompletionArg) = arg match { - case Error(s) => ... - case Response(f) => ... - case Status(code) => ... - } -} -``` -This setup is more complicated than simple overloading of `complete`, but it can still be useful if normal overloading is not available (as in the case above, since we cannot have two overloaded methods that take `Future[...]` arguments), or if normal overloading would lead to a combinatorial explosion of variants. diff --git a/docs/docs/reference/contextual/conversions.md b/docs/docs/reference/contextual/conversions.md index d29eb631e820..7faeb3aac0d5 100644 --- a/docs/docs/reference/contextual/conversions.md +++ b/docs/docs/reference/contextual/conversions.md @@ -3,9 +3,6 @@ layout: doc-page title: "Implicit Conversions" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./conversions-new.html). - Implicit conversions are defined by given instances of the `scala.Conversion` class. This class is defined in package `scala` as follows: ```scala @@ -28,11 +25,11 @@ An implicit conversion is applied automatically by the compiler in three situati 3. In an application `e.m(args)` with `e` of type `T`, if `T` does define some member(s) named `m`, but none of these members can be applied to the arguments `args`. -In the first case, the compiler looks for a given `scala.Conversion` that maps +In the first case, the compiler looks for a given `scala.Conversion` instance that maps an argument of type `T` to type `S`. In the second and third -case, it looks for a given `scala.Conversion` that maps an argument of type `T` +case, it looks for a given `scala.Conversion` instance that maps an argument of type `T` to a type that defines a member `m` which can be applied to `args` if present. -If such an instance `C` is given, the expression `e` is replaced by `C.apply(e)`. +If such an instance `C` is found, the expression `e` is replaced by `C.apply(e)`. ## Examples @@ -40,7 +37,7 @@ If such an instance `C` is given, the expression `e` is replaced by `C.apply(e)` primitive number types to subclasses of `java.lang.Number`. For instance, the conversion from `Int` to `java.lang.Integer` can be defined as follows: ```scala -given int2Integer: Conversion[Int, java.lang.Integer] = +given int2Integer as Conversion[Int, java.lang.Integer] = java.lang.Integer.valueOf(_) ``` @@ -62,9 +59,9 @@ object Completions { // // CompletionArg.fromStatusCode(statusCode) - given fromString : Conversion[String, CompletionArg] = Error(_) - given fromFuture : Conversion[Future[HttpResponse], CompletionArg] = Response(_) - given fromStatusCode : Conversion[Future[StatusCode], CompletionArg] = Status(_) + given fromString as Conversion[String, CompletionArg] = Error(_) + given fromFuture as Conversion[Future[HttpResponse], CompletionArg] = Response(_) + given fromStatusCode as Conversion[Future[StatusCode], CompletionArg] = Status(_) } import CompletionArg._ diff --git a/docs/docs/reference/contextual/delegates.md b/docs/docs/reference/contextual/delegates.md index b5c37fc0e910..9baee04aff65 100644 --- a/docs/docs/reference/contextual/delegates.md +++ b/docs/docs/reference/contextual/delegates.md @@ -3,87 +3,4 @@ layout: doc-page title: "Given Instances" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./givens.html). - -Given instances (or, simply, "givens") define "canonical" values of certain types -that serve for synthesizing arguments to [given clauses](./given-clauses.md). Example: - -```scala -trait Ord[T] { - def compare(x: T, y: T): Int - def (x: T) < (y: T) = compare(x, y) < 0 - def (x: T) > (y: T) = compare(x, y) > 0 -} - -given intOrd: Ord[Int] { - def compare(x: Int, y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 -} - -given listOrd[T](given ord: Ord[T]): Ord[List[T]] { - - def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match { - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = ord.compare(x, y) - if (fst != 0) fst else compare(xs1, ys1) - } -} -``` -This code defines a trait `Ord` with two given instances. `intOrd` defines -a given for the type `Ord[Int]` whereas `listOrd[T]` defines givens -for `Ord[List[T]]` for all types `T` that come with a given instance for `Ord[T]` themselves. -The `(given ord: Ord[T])` clause in `listOrd` defines an implicit parameter. -Given clauses are further explained in the [next section](./given-clauses.md). - -## Anonymous Given Instances - -The name of a given instance can be left out. So the definitions -of the last section can also be expressed like this: -```scala -given Ord[Int] { ... } -given [T](given Ord[T]): Ord[List[T]] { ... } -``` -If the name of a given is missing, the compiler will synthesize a name from -the implemented type(s). - -## Alias Givens - -An alias can be used to define a given instance that is equal to some expression. E.g.: -```scala -given global: ExecutionContext = new ForkJoinPool() -``` -This creates a given `global` of type `ExecutionContext` that resolves to the right -hand side `new ForkJoinPool()`. -The first time `global` is accessed, a new `ForkJoinPool` is created, which is then -returned for this and all subsequent accesses to `global`. - -Alias givens can be anonymous, e.g. -```scala -given Position = enclosingTree.position -given (given outer: Context): Context = outer.withOwner(currentOwner) -``` -An alias given can have type parameters and given clauses just like any other given instance, but it can only implement a single type. - -## Given Instance Initialization - -A given instance without type parameters or given clause is initialized on-demand, the first -time it is accessed. -If a `given` definition has type parameters or a given clause, a fresh instance is created for each reference. - -## Syntax - -Here is the new syntax of given instances, seen as a delta from the [standard context free syntax of Scala 3](../../internals/syntax.md). -``` -TmplDef ::= ... - | ‘given’ GivenDef -GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr - | [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody] -GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause} -GivenParamClause ::= ‘(’ ‘given’ (DefParams | GivenTypes) ‘)’ -GivenTypes ::= Type {‘,’ Type} -``` -The identifier `id` can be omitted only if some types are implemented or the template body defines at least one extension method. +The contents of this page have [moved](./givens.html). diff --git a/docs/docs/reference/contextual/derivation-new.md b/docs/docs/reference/contextual/derivation-new.md deleted file mode 100644 index 47a09a690e25..000000000000 --- a/docs/docs/reference/contextual/derivation-new.md +++ /dev/null @@ -1,402 +0,0 @@ ---- -layout: doc-page -title: Type Class Derivation ---- - -Type class derivation is a way to automatically generate given instances for type classes which satisfy some simple -conditions. A type class in this sense is any trait or class with a type parameter determining the type being operated -on. Common examples are `Eq`, `Ordering`, or `Show`. For example, given the following `Tree` algebraic data type -(ADT), - -```scala -enum Tree[T] derives Eq, Ordering, Show { - case Branch[T](left: Tree[T], right: Tree[T]) - case Leaf[T](elem: T) -} -``` - -The `derives` clause generates the following given instances for the `Eq`, `Ordering` and `Show` type classes in the -companion object of `Tree`, - -```scala -given [T: Eq] as Eq[Tree[T]] = Eq.derived -given [T: Ordering] as Ordering[Tree] = Ordering.derived -given [T: Show] as Show[Tree] = Show.derived -``` - -We say that `Tree` is the _deriving type_ and that the `Eq`, `Ordering` and `Show` instances are _derived instances_. - -### Types supporting `derives` clauses - -All data types can have a `derives` clause. This document focuses primarily on data types which also have a given instance -of the `Mirror` type class available. Given instances of the `Mirror` type class are generated automatically by the compiler -for, - -+ enums and enum cases -+ case classes and case objects -+ sealed classes or traits that have only case classes and case objects as children - -`Mirror` type class instances provide information at the type level about the components and labelling of the type. -They also provide minimal term level infrastructure to allow higher level libraries to provide comprehensive -derivation support. - -```scala -sealed trait Mirror { - - /** the type being mirrored */ - type MirroredType - - /** the type of the elements of the mirrored type */ - type MirroredElemTypes - - /** The mirrored *-type */ - type MirroredMonoType - - /** The name of the type */ - type MirroredLabel <: String - - /** The names of the elements of the type */ - type MirroredElemLabels <: Tuple -} - -object Mirror { - /** The Mirror for a product type */ - trait Product extends Mirror { - - /** Create a new instance of type `T` with elements taken from product `p`. */ - def fromProduct(p: scala.Product): MirroredMonoType - } - - trait Sum extends Mirror { self => - /** The ordinal number of the case class of `x`. For enums, `ordinal(x) == x.ordinal` */ - def ordinal(x: MirroredMonoType): Int - } -} -``` - -Product types (i.e. case classes and objects, and enum cases) have mirrors which are subtypes of `Mirror.Product`. Sum -types (i.e. sealed class or traits with product children, and enums) have mirrors which are subtypes of `Mirror.Sum`. - -For the `Tree` ADT from above the following `Mirror` instances will be automatically provided by the compiler, - -```scala -// Mirror for Tree -Mirror.Sum { - type MirroredType = Tree - type MirroredElemTypes[T] = (Branch[T], Leaf[T]) - type MirroredMonoType = Tree[_] - type MirroredLabels = "Tree" - type MirroredElemLabels = ("Branch", "Leaf") - - def ordinal(x: MirroredMonoType): Int = x match { - case _: Branch[_] => 0 - case _: Leaf[_] => 1 - } -} - -// Mirror for Branch -Mirror.Product { - type MirroredType = Branch - type MirroredElemTypes[T] = (Tree[T], Tree[T]) - type MirroredMonoType = Branch[_] - type MirroredLabels = "Branch" - type MirroredElemLabels = ("left", "right") - - def fromProduct(p: Product): MirroredMonoType = - new Branch(...) -} - -// Mirror for Leaf -Mirror.Product { - type MirroredType = Leaf - type MirroredElemTypes[T] = Tuple1[T] - type MirroredMonoType = Leaf[_] - type MirroredLabels = "Leaf" - type MirroredElemLabels = Tuple1["elem"] - - def fromProduct(p: Product): MirroredMonoType = - new Leaf(...) -} -``` - -Note the following properties of `Mirror` types, - -+ Properties are encoded using types rather than terms. This means that they have no runtime footprint unless used and - also that they are a compile time feature for use with Dotty's metaprogramming facilities. -+ The kinds of `MirroredType` and `MirroredElemTypes` match the kind of the data type the mirror is an instance for. - This allows `Mirrors` to support ADTs of all kinds. -+ There is no distinct representation type for sums or products (ie. there is no `HList` or `Coproduct` type as in - Scala 2 versions of shapeless). Instead the collection of child types of a data type is represented by an ordinary, - possibly parameterized, tuple type. Dotty's metaprogramming facilities can be used to work with these tuple types - as-is, and higher level libraries can be built on top of them. -+ The methods `ordinal` and `fromProduct` are defined in terms of `MirroredMonoType` which is the type of kind-`*` - which is obtained from `MirroredType` by wildcarding its type parameters. - -### Type classes supporting automatic deriving - -A trait or class can appear in a `derives` clause if its companion object defines a method named `derived`. The -signature and implementation of a `derived` method for a type class `TC[_]` are arbitrary but it is typically of the -following form, - -```scala -def derived[T](using Mirror.Of[T]): TC[T] = ... -``` - -That is, the `derived` method takes a context parameter of (some subtype of) type `Mirror` which defines the shape of -the deriving type `T`, and computes the type class implementation according to that shape. This is all that the -provider of an ADT with a `derives` clause has to know about the derivation of a type class instance. - -Note that `derived` methods may have context `Mirror` parameters indirectly (e.g. by having a context argument which in turn -has a context `Mirror` parameter, or not at all (e.g. they might use some completely different user-provided mechanism, for -instance using Dotty macros or runtime reflection). We expect that (direct or indirect) `Mirror` based implementations -will be the most common and that is what this document emphasises. - -Type class authors will most likely use higher level derivation or generic programming libraries to implement -`derived` methods. An example of how a `derived` method might be implemented using _only_ the low level facilities -described above and Dotty's general metaprogramming features is provided below. It is not anticipated that type class -authors would normally implement a `derived` method in this way, however this walkthrough can be taken as a guide for -authors of the higher level derivation libraries that we expect typical type class authors will use (for a fully -worked out example of such a library, see [shapeless 3](https://github.com/milessabin/shapeless/tree/shapeless-3)). - -#### How to write a type class `derived` method using low level mechanisms - -The low-level method we will use to implement a type class `derived` method in this example exploits three new -type-level constructs in Dotty: inline methods, inline matches, and implicit searches via `summonFrom`. Given this definition of the -`Eq` type class, - - -```scala -trait Eq[T] { - def eqv(x: T, y: T): Boolean -} -``` - -we need to implement a method `Eq.derived` on the companion object of `Eq` that produces a given instance for `Eq[T]` given -a `Mirror[T]`. Here is a possible implementation, - -```scala -inline given derived[T](using m: Mirror.Of[T]) as Eq[T] = { - val elemInstances = summonAll[m.MirroredElemTypes] // (1) - inline m match { // (2) - case s: Mirror.SumOf[T] => eqSum(s, elemInstances) - case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances) - } -} -``` - -Note that `derived` is defined as an `inline` given. This means that the method will be expanded at -call sites (for instance the compiler generated instance definitions in the companion objects of ADTs which have a -`derived Eq` clause), and also that it can be used recursively if necessary, to compute instances for children. - -The body of this method (1) first materializes the `Eq` instances for all the child types of type the instance is -being derived for. This is either all the branches of a sum type or all the fields of a product type. The -implementation of `summonAll` is `inline` and uses Dotty's `summonFrom` construct to collect the instances as a -`List`, - -```scala -inline def summonAll[T]: T = summonFrom { - case t: T => t -} - -inline def summonAll[T <: Tuple]: List[Eq[_]] = inline erasedValue[T] match { - case _: Unit => Nil - case _: (t *: ts) => summon[Eq[t]] :: summonAll[ts] -} -``` - -with the instances for children in hand the `derived` method uses an `inline match` to dispatch to methods which can -construct instances for either sums or products (2). Note that because `derived` is `inline` the match will be -resolved at compile-time and only the left-hand side of the matching case will be inlined into the generated code with -types refined as revealed by the match. - -In the sum case, `eqSum`, we use the runtime `ordinal` values of the arguments to `eqv` to first check if the two -values are of the same subtype of the ADT (3) and then, if they are, to further test for equality based on the `Eq` -instance for the appropriate ADT subtype using the auxiliary method `check` (4). - -```scala -def eqSum[T](s: Mirror.SumOf[T], elems: List[Eq[_]]): Eq[T] = - new Eq[T] { - def eqv(x: T, y: T): Boolean = { - val ordx = s.ordinal(x) // (3) - (s.ordinal(y) == ordx) && check(elems(ordx))(x, y) // (4) - } - } -``` - -In the product case, `eqProduct` we test the runtime values of the arguments to `eqv` for equality as products based -on the `Eq` instances for the fields of the data type (5), - -```scala -def eqProduct[T](p: Mirror.ProductOf[T], elems: List[Eq[_]]): Eq[T] = - new Eq[T] { - def eqv(x: T, y: T): Boolean = - iterator(x).zip(iterator(y)).zip(elems.iterator).forall { // (5) - case ((x, y), elem) => check(elem)(x, y) - } - } -``` - -Pulling this all together we have the following complete implementation, - -```scala -import scala.deriving._ -import scala.compiletime.{erasedValue, summonFrom} - -inline def summon[T]: T = summonFrom { - case t: T => t -} - -inline def summonAll[T <: Tuple]: List[Eq[_]] = inline erasedValue[T] match { - case _: Unit => Nil - case _: (t *: ts) => summon[Eq[t]] :: summonAll[ts] -} - -trait Eq[T] { - def eqv(x: T, y: T): Boolean -} - -object Eq { - given Eq[Int] as { - def eqv(x: Int, y: Int) = x == y - } - - def check(elem: Eq[_])(x: Any, y: Any): Boolean = - elem.asInstanceOf[Eq[Any]].eqv(x, y) - - def iterator[T](p: T) = p.asInstanceOf[Product].productIterator - - def eqSum[T](s: Mirror.SumOf[T], elems: List[Eq[_]]): Eq[T] = - new Eq[T] { - def eqv(x: T, y: T): Boolean = { - val ordx = s.ordinal(x) - (s.ordinal(y) == ordx) && check(elems(ordx))(x, y) - } - } - - def eqProduct[T](p: Mirror.ProductOf[T], elems: List[Eq[_]]): Eq[T] = - new Eq[T] { - def eqv(x: T, y: T): Boolean = - iterator(x).zip(iterator(y)).zip(elems.iterator).forall { - case ((x, y), elem) => check(elem)(x, y) - } - } - - inline given derived[T](using m: Mirror.Of[T]) as Eq[T] = { - val elemInstances = summonAll[m.MirroredElemTypes] - inline m match { - case s: Mirror.SumOf[T] => eqSum(s, elemInstances) - case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances) - } - } -} -``` - -we can test this relative to a simple ADT like so, - -```scala -enum Opt[+T] derives Eq { - case Sm(t: T) - case Nn -} - -object Test extends App { - import Opt._ - val eqoi = summon[Eq[Opt[Int]]] - assert(eqoi.eqv(Sm(23), Sm(23))) - assert(!eqoi.eqv(Sm(23), Sm(13))) - assert(!eqoi.eqv(Sm(23), Nn)) -} -``` - -In this case the code that is generated by the inline expansion for the derived `Eq` instance for `Opt` looks like the -following, after a little polishing, - -```scala -given derived$Eq[T](using eqT: Eq[T]) as Eq[Opt[T]] = - eqSum(summon[Mirror[Opt[T]]], - List( - eqProduct(summon[Mirror[Sm[T]]], List(summon[Eq[T]])) - eqProduct(summon[Mirror[Nn.type]], Nil) - ) - ) -``` - -Alternative approaches can be taken to the way that `derived` methods can be defined. For example, more aggressively -inlined variants using Dotty macros, whilst being more involved for type class authors to write than the example -above, can produce code for type classes like `Eq` which eliminate all the abstraction artefacts (eg. the `Lists` of -child instances in the above) and generate code which is indistinguishable from what a programmer might write by hand. -As a third example, using a higher level library such as shapeless the type class author could define an equivalent -`derived` method as, - -```scala -given eqSum[A](using inst: => K0.CoproductInstances[Eq, A]) as Eq[A] { - def eqv(x: A, y: A): Boolean = inst.fold2(x, y)(false)( - [t] => (eqt: Eq[t], t0: t, t1: t) => eqt.eqv(t0, t1) - ) -} - -given eqProduct[A](using inst: K0.ProductInstances[Eq, A]) as Eq[A] { - def eqv(x: A, y: A): Boolean = inst.foldLeft2(x, y)(true: Boolean)( - [t] => (acc: Boolean, eqt: Eq[t], t0: t, t1: t) => Complete(!eqt.eqv(t0, t1))(false)(true) - ) -} - -inline def derived[A](using gen: K0.Generic[A]) as Eq[A] = gen.derive(eqSum, eqProduct) -``` - -The framework described here enables all three of these approaches without mandating any of them. - -For a brief discussion on how to use macros to write a type class `derived` -method please read more at [How to write a type class `derived` method using -macros](./derivation-macro.md). - -### Deriving instances elsewhere - -Sometimes one would like to derive a type class instance for an ADT after the ADT is defined, without being able to -change the code of the ADT itself. To do this, simply define an instance using the `derived` method of the type class -as right-hand side. E.g, to implement `Ordering` for `Option` define, - -```scala -given [T: Ordering] as Ordering[Option[T]] = Ordering.derived -``` - -Assuming the `Ordering.derived` method has a context parameter of type `Mirror[T]` it will be satisfied by the -compiler generated `Mirror` instance for `Option` and the derivation of the instance will be expanded on the right -hand side of this definition in the same way as an instance defined in ADT companion objects. - -### Syntax - -``` -Template ::= InheritClauses [TemplateBody] -EnumDef ::= id ClassConstr InheritClauses EnumBody -InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] -ConstrApps ::= ConstrApp {‘with’ ConstrApp} - | ConstrApp {‘,’ ConstrApp} -``` - -### Discussion - -This type class derivation framework is intentionally very small and low-level. There are essentially two pieces of -infrastructure in compiler-generated `Mirror` instances, - -+ type members encoding properties of the mirrored types. -+ a minimal value level mechanism for working generically with terms of the mirrored types. - -The `Mirror` infrastructure can be seen as an extension of the existing `Product` infrastructure for case classes: -typically `Mirror` types will be implemented by the ADTs companion object, hence the type members and the `ordinal` or -`fromProduct` methods will be members of that object. The primary motivation for this design decision, and the -decision to encode properties via types rather than terms was to keep the bytecode and runtime footprint of the -feature small enough to make it possible to provide `Mirror` instances _unconditionally_. - -Whilst `Mirrors` encode properties precisely via type members, the value level `ordinal` and `fromProduct` are -somewhat weakly typed (because they are defined in terms of `MirroredMonoType`) just like the members of `Product`. -This means that code for generic type classes has to ensure that type exploration and value selection proceed in -lockstep and it has to assert this conformance in some places using casts. If generic type classes are correctly -written these casts will never fail. - -As mentioned, however, the compiler-provided mechansim is intentionally very low level and it is anticipated that -higher level type class derivation and generic programming libraries will build on this and Dotty's other -metaprogramming facilities to hide these low-level details from type class authors and general users. Type class -derivation in the style of both shapeless and Magnolia are possible (a prototype of shapeless 3, which combines -aspects of both shapeless 2 and Magnolia has been developed alongside this language feature) as is a more aggressively -inlined style, supported by Dotty's new quote/splice macro and inlining facilities. diff --git a/docs/docs/reference/contextual/derivation.md b/docs/docs/reference/contextual/derivation.md index 5074ffedb5de..a446500c1037 100644 --- a/docs/docs/reference/contextual/derivation.md +++ b/docs/docs/reference/contextual/derivation.md @@ -3,9 +3,6 @@ layout: doc-page title: Type Class Derivation --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./derivation-new.html). - Type class derivation is a way to automatically generate given instances for type classes which satisfy some simple conditions. A type class in this sense is any trait or class with a type parameter determining the type being operated on. Common examples are `Eq`, `Ordering`, or `Show`. For example, given the following `Tree` algebraic data type @@ -22,16 +19,16 @@ The `derives` clause generates the following given instances for the `Eq`, `Orde companion object of `Tree`, ```scala -given [T: Eq] : Eq[Tree[T]] = Eq.derived -given [T: Ordering] : Ordering[Tree] = Ordering.derived -given [T: Show] : Show[Tree] = Show.derived +given [T: Eq] as Eq[Tree[T]] = Eq.derived +given [T: Ordering] as Ordering[Tree] = Ordering.derived +given [T: Show] as Show[Tree] = Show.derived ``` We say that `Tree` is the _deriving type_ and that the `Eq`, `Ordering` and `Show` instances are _derived instances_. ### Types supporting `derives` clauses -All data types can have a `derives` clause. This document focuses primarily on data types which also have an instance +All data types can have a `derives` clause. This document focuses primarily on data types which also have a given instance of the `Mirror` type class available. Instances of the `Mirror` type class are generated automatically by the compiler for, @@ -142,15 +139,15 @@ signature and implementation of a `derived` method for a type class `TC[_]` are following form, ```scala -def derived[T](given Mirror.Of[T]): TC[T] = ... +def derived[T](using Mirror.Of[T]): TC[T] = ... ``` -That is, the `derived` method takes a given parameter of (some subtype of) type `Mirror` which defines the shape of +That is, the `derived` method takes a context parameter of (some subtype of) type `Mirror` which defines the shape of the deriving type `T`, and computes the type class implementation according to that shape. This is all that the provider of an ADT with a `derives` clause has to know about the derivation of a type class instance. -Note that `derived` methods may have given `Mirror` arguments indirectly (e.g. by having a given argument which in turn -has a given `Mirror`, or not at all (e.g. they might use some completely different user-provided mechanism, for +Note that `derived` methods may have context `Mirror` parameters indirectly (e.g. by having a context argument which in turn +has a context `Mirror` parameter, or not at all (e.g. they might use some completely different user-provided mechanism, for instance using Dotty macros or runtime reflection). We expect that (direct or indirect) `Mirror` based implementations will be the most common and that is what this document emphasises. @@ -174,11 +171,11 @@ trait Eq[T] { } ``` -we need to implement a method `Eq.derived` on the companion object of `Eq` that produces an instance for `Eq[T]` given +we need to implement a method `Eq.derived` on the companion object of `Eq` that produces a given instance for `Eq[T]` given a `Mirror[T]`. Here is a possible implementation, ```scala -inline given derived[T](given m: Mirror.Of[T]): Eq[T] = { +inline given derived[T](using m: Mirror.Of[T]) as Eq[T] = { val elemInstances = summonAll[m.MirroredElemTypes] // (1) inline m match { // (2) case s: Mirror.SumOf[T] => eqSum(s, elemInstances) @@ -187,17 +184,17 @@ inline given derived[T](given m: Mirror.Of[T]): Eq[T] = { } ``` -Note that the `derived` method is defined as both `inline` and `given`. This means that the method will be expanded at +Note that `derived` is defined as an `inline` given. This means that the method will be expanded at call sites (for instance the compiler generated instance definitions in the companion objects of ADTs which have a -`derived Eq` clause), and also that it can be used recursively if necessary, to compute instance for children. +`derived Eq` clause), and also that it can be used recursively if necessary, to compute instances for children. The body of this method (1) first materializes the `Eq` instances for all the child types of type the instance is being derived for. This is either all the branches of a sum type or all the fields of a product type. The -implementation of `summonAll` is `inline` and uses Dotty's `given match` construct to collect the instances as a +implementation of `summonAll` is `inline` and uses Dotty's `summonFrom` construct to collect the instances as a `List`, ```scala -inline def summon[T]: T = given match { +inline def summonAll[T]: T = summonFrom { case t: T => t } @@ -243,9 +240,9 @@ Pulling this all together we have the following complete implementation, ```scala import scala.deriving._ -import scala.compiletime.erasedValue +import scala.compiletime.{erasedValue, summonFrom} -inline def summon[T]: T = given match { +inline def summon[T]: T = summonFrom { case t: T => t } @@ -259,7 +256,7 @@ trait Eq[T] { } object Eq { - given Eq[Int] { + given Eq[Int] as { def eqv(x: Int, y: Int) = x == y } @@ -284,7 +281,7 @@ object Eq { } } - inline given derived[T](given m: Mirror.Of[T]): Eq[T] = { + inline given derived[T](using m: Mirror.Of[T]) as Eq[T] = { val elemInstances = summonAll[m.MirroredElemTypes] inline m match { case s: Mirror.SumOf[T] => eqSum(s, elemInstances) @@ -315,7 +312,7 @@ In this case the code that is generated by the inline expansion for the derived following, after a little polishing, ```scala -given derived$Eq[T](given eqT: Eq[T]): Eq[Opt[T]] = +given derived$Eq[T](using eqT: Eq[T]) as Eq[Opt[T]] = eqSum(summon[Mirror[Opt[T]]], List( eqProduct(summon[Mirror[Sm[T]]], List(summon[Eq[T]])) @@ -332,20 +329,19 @@ As a third example, using a higher level library such as shapeless the type clas `derived` method as, ```scala -given eqSum[A](given inst: => K0.CoproductInstances[Eq, A]): Eq[A] { +given eqSum[A](using inst: => K0.CoproductInstances[Eq, A]) as Eq[A] { def eqv(x: A, y: A): Boolean = inst.fold2(x, y)(false)( [t] => (eqt: Eq[t], t0: t, t1: t) => eqt.eqv(t0, t1) ) } -given eqProduct[A](given inst: K0.ProductInstances[Eq, A]): Eq[A] { +given eqProduct[A](using inst: K0.ProductInstances[Eq, A]) as Eq[A] { def eqv(x: A, y: A): Boolean = inst.foldLeft2(x, y)(true: Boolean)( [t] => (acc: Boolean, eqt: Eq[t], t0: t, t1: t) => Complete(!eqt.eqv(t0, t1))(false)(true) ) } - -inline def derived[A](given gen: K0.Generic[A]): Eq[A] = gen.derive(eqSum, eqProduct) +inline def derived[A](using gen: K0.Generic[A]) as Eq[A] = gen.derive(eqSum, eqProduct) ``` The framework described here enables all three of these approaches without mandating any of them. @@ -361,10 +357,10 @@ change the code of the ADT itself. To do this, simply define an instance using as right-hand side. E.g, to implement `Ordering` for `Option` define, ```scala -given [T: Ordering] : Ordering[Option[T]] = Ordering.derived +given [T: Ordering] as Ordering[Option[T]] = Ordering.derived ``` -Assuming the `Ordering.derived` method has a given parameter of type `Mirror[T]` it will be satisfied by the +Assuming the `Ordering.derived` method has a context parameter of type `Mirror[T]` it will be satisfied by the compiler generated `Mirror` instance for `Option` and the derivation of the instance will be expanded on the right hand side of this definition in the same way as an instance defined in ADT companion objects. diff --git a/docs/docs/reference/contextual/given-clauses.md b/docs/docs/reference/contextual/given-clauses.md index 6fbd30aa4c3e..eff0a8cd3e48 100644 --- a/docs/docs/reference/contextual/given-clauses.md +++ b/docs/docs/reference/contextual/given-clauses.md @@ -3,117 +3,5 @@ layout: doc-page title: "Given Parameters" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./context-parameters.html). +The contents of this page have [moved](./using-clauses.html). -Functional programming tends to express most dependencies as simple function parameterization. -This is clean and powerful, but it sometimes leads to functions that take many parameters and -call trees where the same value is passed over and over again in long call chains to many -functions. Given clauses can help here since they enable the compiler to synthesize -repetitive arguments instead of the programmer having to write them explicitly. - -For example, with the [given instances](./delegates.md) defined previously, -a maximum function that works for any arguments for which an ordering exists can be defined as follows: -```scala -def max[T](x: T, y: T)(given ord: Ord[T]): T = - if (ord.compare(x, y) < 0) y else x -``` -Here, `ord` is an _implicit parameter_ introduced with a `given` clause. -The `max` method can be applied as follows: -```scala -max(2, 3)(given intOrd) -``` -The `(given intOrd)` part passes `intOrd` as an argument for the `ord` parameter. But the point of -implicit parameters is that this argument can also be left out (and it usually is). So the following -applications are equally valid: -```scala -max(2, 3) -max(List(1, 2, 3), Nil) -``` - -## Anonymous Given Clauses - -In many situations, the name of an implicit parameter need not be -mentioned explicitly at all, since it is used only in synthesized arguments for -other implicit parameters. In that case one can avoid defining a parameter name -and just provide its type. Example: -```scala -def maximum[T](xs: List[T])(given Ord[T]): T = - xs.reduceLeft(max) -``` -`maximum` takes an implicit parameter of type `Ord` only to pass it on as an -inferred argument to `max`. The name of the parameter is left out. - -Generally, implicit parameters may be defined either as a full parameter list `(given p_1: T_1, ..., p_n: T_n)` or just as a sequence of types `(given T_1, ..., T_n)`. -Vararg given parameters are not supported. - -## Inferring Complex Arguments - -Here are two other methods that have an implicit parameter of type `Ord[T]`: -```scala -def descending[T](given asc: Ord[T]): Ord[T] = new Ord[T] { - def compare(x: T, y: T) = asc.compare(y, x) -} - -def minimum[T](xs: List[T])(given Ord[T]) = - maximum(xs)(given descending) -``` -The `minimum` method's right hand side passes `descending` as an explicit argument to `maximum(xs)`. -With this setup, the following calls are all well-formed, and they all normalize to the last one: -```scala -minimum(xs) -maximum(xs)(given descending) -maximum(xs)(given descending(given listOrd)) -maximum(xs)(given descending(given listOrd(given intOrd))) -``` - -## Multiple Given Clauses - -There can be several `given` parameter clauses in a definition and `given` parameter clauses can be freely -mixed with normal ones. Example: -```scala -def f(u: Universe)(given ctx: u.Context)(given s: ctx.Symbol, k: ctx.Kind) = ... -``` -Multiple given clauses are matched left-to-right in applications. Example: -```scala -object global extends Universe { type Context = ... } -given ctx : global.Context { type Symbol = ...; type Kind = ... } -given sym : ctx.Symbol -given kind : ctx.Kind -``` -Then the following calls are all valid (and normalize to the last one) -```scala -f -f(global) -f(global)(given ctx) -f(global)(given ctx)(given sym, kind) -``` -But `f(global)(given sym, kind)` would give a type error. - -## Summoning Instances - -The method `summon` in `Predef` returns the given instance of a specific type. For example, -the given instance for `Ord[List[Int]]` is produced by -```scala -summon[Ord[List[Int]]] // reduces to listOrd given intOrd -``` -The `summon` method is simply defined as the (non-widening) identity function over an implicit parameter. -```scala -def summon[T](given x: T): x.type = x -``` - -## Syntax - -Here is the new syntax of parameters and arguments seen as a delta from the [standard context free syntax of Scala 3](../../internals/syntax.md). -``` -ClsParamClauses ::= ... - | {ClsParamClause} {GivenClsParamClause} -GivenClsParamClause ::= ‘(’ ‘given’ (ClsParams | GivenTypes) ‘)’ -DefParamClauses ::= ... - | {DefParamClause} {GivenParamClause} -GivenParamClause ::= ‘(’ ‘given’ (DefParams | GivenTypes) ‘)’ -GivenTypes ::= AnnotType {‘,’ AnnotType} - -ParArgumentExprs ::= ... - | ‘(’ ‘given’ ExprsInParens ‘)’ -``` diff --git a/docs/docs/reference/contextual/givens.md b/docs/docs/reference/contextual/givens.md index 939d91e6c2ce..072f3cb4a800 100644 --- a/docs/docs/reference/contextual/givens.md +++ b/docs/docs/reference/contextual/givens.md @@ -4,7 +4,7 @@ title: "Given Instances" --- Given instances (or, simply, "givens") define "canonical" values of certain types -that serve for synthesizing arguments to [context parameters](./context-parameters.md). Example: +that serve for synthesizing arguments to [context parameters](./using-clauses.html). Example: ```scala trait Ord[T] { @@ -35,7 +35,7 @@ for `Ord[List[T]]` for all types `T` that come with a given instance for `Ord[T] themselves. The `using` clause in `listOrd` defines a condition: There must be a given of type `Ord[T]` for a given of type `List[Ord[T]]` to exist. Such conditions are expanded by the compiler to context -parameters, which are explained in the [next section](./context-parameters.md). +parameters, which are explained in the [next section](./using-clauses.html). ## Anonymous Givens diff --git a/docs/docs/reference/contextual/implicit-by-name-parameters.md b/docs/docs/reference/contextual/implicit-by-name-parameters.md index 4c723f647434..45f0677b413c 100644 --- a/docs/docs/reference/contextual/implicit-by-name-parameters.md +++ b/docs/docs/reference/contextual/implicit-by-name-parameters.md @@ -3,68 +3,5 @@ layout: doc-page title: "Implicit By-Name Parameters" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./by-name-context-parameters.html). +The contents of this page have [moved](./by-name-context-parameters.html). -Implicit parameters can be declared by-name to avoid a divergent inferred expansion. Example: - -```scala -trait Codec[T] { - def write(x: T): Unit -} - -given intCodec: Codec[Int] = ??? - -given optionCodec[T](given ev: => Codec[T]): Codec[Option[T]] { - def write(xo: Option[T]) = xo match { - case Some(x) => ev.write(x) - case None => - } -} - -val s = summon[Codec[Option[Int]]] - -s.write(Some(33)) -s.write(None) -``` -As is the case for a normal by-name parameter, the argument for the implicit parameter `ev` -is evaluated on demand. In the example above, if the option value `x` is `None`, it is -not evaluated at all. - -The synthesized argument for an implicit parameter is backed by a local val -if this is necessary to prevent an otherwise diverging expansion. - -The precise steps for synthesizing an argument for an implicit by-name parameter of type `=> T` are as follows. - - 1. Create a new given instance of type `T`: - - ```scala - given lv: T = ??? - ``` - where `lv` is an arbitrary fresh name. - - 1. This given instance is not immediately available as candidate for argument inference (making it immediately available could result in a loop in the synthesized computation). But it becomes available in all nested contexts that look again for an argument to an implicit by-name parameter. - - 1. If this search succeeds with expression `E`, and `E` contains references to `lv`, replace `E` by - - - ```scala - { given lv: T = E; lv } - ``` - - Otherwise, return `E` unchanged. - -In the example above, the definition of `s` would be expanded as follows. - -```scala -val s = summon[Test.Codec[Option[Int]]]( - optionCodec[Int](intCodec) -) -``` - -No local given instance was generated because the synthesized argument is not recursive. - -### Reference - -For more info, see [Issue #1998](https://github.com/lampepfl/dotty/issues/1998) -and the associated [Scala SIP](https://docs.scala-lang.org/sips/byname-implicits.html). diff --git a/docs/docs/reference/contextual/implicit-function-types-spec.md b/docs/docs/reference/contextual/implicit-function-types-spec.md index c87a37499199..64e0d3c2126b 100644 --- a/docs/docs/reference/contextual/implicit-function-types-spec.md +++ b/docs/docs/reference/contextual/implicit-function-types-spec.md @@ -3,79 +3,5 @@ layout: doc-page title: "Implicit Function Types - More Details" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./context-functions-spec.html). +The contents of this page have [moved](./context-functions-spec.html). - -## Syntax - - Type ::= ... - | FunArgTypes ‘=>’ Typee - FunArgTypes ::= InfixType - | ‘(’ [ ‘[given]’ FunArgType {‘,’ FunArgType } ] ‘)’ - | ‘(’ ‘[given]’ TypedFunParam {‘,’ TypedFunParam } ‘)’ - Bindings ::= ‘(’ [[‘given’] Binding {‘,’ Binding}] ‘)’ - -Implicit function types associate to the right, e.g. -`(given S) => (given T) => U` is the same as `(given S) => ((given T) => U)`. - -## Implementation - -Implicit function types are shorthands for class types that define `apply` -methods with implicit parameters. Specifically, the `N`-ary function type -`T1, ..., TN => R` is a shorthand for the class type -`ImplicitFunctionN[T1 , ... , TN, R]`. Such class types are assumed to have the following definitions, for any value of `N >= 1`: -```scala -package scala -trait ImplicitFunctionN[-T1 , ... , -TN, +R] { - def apply(given x1: T1 , ... , xN: TN): R -} -``` -Implicit function types erase to normal function types, so these classes are -generated on the fly for typechecking, but not realized in actual code. - -Implicit function literals `(given x1: T1, ..., xn: Tn) => e` map -implicit parameters `xi` of types `Ti` to the result of evaluating the expression `e`. -The scope of each implicit parameter `xi` is `e`. The parameters must have pairwise distinct names. - -If the expected type of the implicit function literal is of the form -`scala.ImplicitFunctionN[S1, ..., Sn, R]`, the expected type of `e` is `R` and -the type `Ti` of any of the parameters `xi` can be omitted, in which case `Ti -= Si` is assumed. If the expected type of the implicit function literal is -some other type, all implicit parameter types must be explicitly given, and the expected type of `e` is undefined. -The type of the implicit function literal is `scala.ImplicitFunctionN[S1, ...,Sn, T]`, where `T` is the widened -type of `e`. `T` must be equivalent to a type which does not refer to any of -the implicit parameters `xi`. - -The implicit function literal is evaluated as the instance creation -expression -```scala -new scala.ImplicitFunctionN[T1, ..., Tn, T] { - def apply(given x1: T1, ..., xn: Tn): T = e -} -``` -An implicit parameter may also be a wildcard represented by an underscore `_`. In -that case, a fresh name for the parameter is chosen arbitrarily. - -Note: The closing paragraph of the -[Anonymous Functions section](https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#anonymous-functions) -of Scala 2.12 is subsumed by implicit function types and should be removed. - -Implicit function literals `(given x1: T1, ..., xn: Tn) => e` are -automatically created for any expression `e` whose expected type is -`scala.ImplicitFunctionN[T1, ..., Tn, R]`, unless `e` is -itself a implicit function literal. This is analogous to the automatic -insertion of `scala.Function0` around expressions in by-name argument position. - -Implicit function types generalize to `N > 22` in the same way that function types do, see [the corresponding -documentation](../dropped-features/limit22.md). - -## Examples - -See the section on Expressiveness from [Simplicitly: foundations and -applications of implicit function -types](https://dl.acm.org/citation.cfm?id=3158130). - -### Type Checking - -After desugaring no additional typing rules are required for implicit function types. diff --git a/docs/docs/reference/contextual/implicit-function-types.md b/docs/docs/reference/contextual/implicit-function-types.md index 80391b4fcd7e..813777b66ce3 100644 --- a/docs/docs/reference/contextual/implicit-function-types.md +++ b/docs/docs/reference/contextual/implicit-function-types.md @@ -3,154 +3,4 @@ layout: doc-page title: "Implicit Function Types" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./context-functions.html). - -_Implicit functions_ are functions with (only) implicit parameters. -Their types are _implicit function types_. Here is an example of an implicit function type: - -```scala -type Executable[T] = (given ExecutionContext) => T -``` -An implicit function is applied to synthesized arguments, in -the same way a method with a given clause is applied. For instance: -```scala - given ec: ExecutionContext = ... - - def f(x: Int): Executable[Int] = ... - - f(2)(given ec) // explicit argument - f(2) // argument is inferred -``` -Conversely, if the expected type of an expression `E` is an implicit function type -`(given T_1, ..., T_n) => U` and `E` is not already an -implicit function literal, `E` is converted to an implicit function literal by rewriting to -```scala - (given x_1: T1, ..., x_n: Tn) => E -``` -where the names `x_1`, ..., `x_n` are arbitrary. This expansion is performed -before the expression `E` is typechecked, which means that `x_1`, ..., `x_n` -are available as givens in `E`. - -Like their types, implicit function literals are written with a `given` prefix. They differ from normal function literals in two ways: - - 1. Their parameters are defined with a given clause. - 2. Their types are implicit function types. - -For example, continuing with the previous definitions, -```scala - def g(arg: Executable[Int]) = ... - - g(22) // is expanded to g((given ev) => 22) - - g(f(2)) // is expanded to g((given ev) => f(2)(given ev)) - - g((given ctx) => f(22)(given ctx)) // is left as it is -``` -### Example: Builder Pattern - -Implicit function types have considerable expressive power. For -instance, here is how they can support the "builder pattern", where -the aim is to construct tables like this: -```scala - table { - row { - cell("top left") - cell("top right") - } - row { - cell("bottom left") - cell("bottom right") - } - } -``` -The idea is to define classes for `Table` and `Row` that allow -addition of elements via `add`: -```scala - class Table { - val rows = new ArrayBuffer[Row] - def add(r: Row): Unit = rows += r - override def toString = rows.mkString("Table(", ", ", ")") - } - - class Row { - val cells = new ArrayBuffer[Cell] - def add(c: Cell): Unit = cells += c - override def toString = cells.mkString("Row(", ", ", ")") - } - - case class Cell(elem: String) -``` -Then, the `table`, `row` and `cell` constructor methods can be defined -with implicit function types as parameters to avoid the plumbing boilerplate -that would otherwise be necessary. -```scala - def table(init: (given Table) => Unit) = { - given t: Table - init - t - } - - def row(init: (given Row) => Unit)(given t: Table) = { - given r: Row - init - t.add(r) - } - - def cell(str: String)(given r: Row) = - r.add(new Cell(str)) -``` -With that setup, the table construction code above compiles and expands to: -```scala - table { (given $t: Table) => - row { (given $r: Row) => - cell("top left")(given $r) - cell("top right")(given $r) - } (given $t) - row { (given $r: Row) => - cell("bottom left")(given $r) - cell("bottom right")(given $r) - } (given $t) - } -``` -### Example: Postconditions - -As a larger example, here is a way to define constructs for checking arbitrary postconditions using an extension method `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extension methods to provide a zero-overhead abstraction. - -```scala -object PostConditions { - opaque type WrappedResult[T] = T - - def result[T](given r: WrappedResult[T]): T = r - - def (x: T).ensuring[T](condition: (given WrappedResult[T]) => Boolean): T = { - assert(condition(given x)) - x - } -} -import PostConditions.{ensuring, result} - -val s = List(1, 2, 3).sum.ensuring(result == 6) -``` -**Explanations**: We use an implicit function type `(given WrappedResult[T]) => Boolean` -as the type of the condition of `ensuring`. An argument to `ensuring` such as -`(result == 6)` will therefore have a given instance of type `WrappedResult[T]` in -scope to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure -that we do not get unwanted givens in scope (this is good practice in all cases -where implicit parameters are involved). Since `WrappedResult` is an opaque type alias, its -values need not be boxed, and since `ensuring` is added as an extension method, its argument -does not need boxing either. Hence, the implementation of `ensuring` is as about as efficient -as the best possible code one could write by hand: - -```scala -{ val result = List(1, 2, 3).sum - assert(result == 6) - result -} -``` -### Reference - -For more info, see the [blog article](https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html), -(which uses a different syntax that has been superseded). - -[More details](./implicit-function-types-spec.md) +The contents of this page have [moved](./context-functions.html). \ No newline at end of file diff --git a/docs/docs/reference/contextual/import-delegate.md b/docs/docs/reference/contextual/import-delegate.md index 8f07167238a0..d3e9345492ed 100644 --- a/docs/docs/reference/contextual/import-delegate.md +++ b/docs/docs/reference/contextual/import-delegate.md @@ -3,117 +3,5 @@ layout: doc-page title: "Import Given" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./given-imports.html). +The contents of this page have [moved](./given-imports.html). -A special form of import wildcard selector is used to import given instances. Example: -```scala -object A { - class TC - given tc: TC - def f(given TC) = ??? -} -object B { - import A._ - import A.given -} -``` -In the code above, the `import A._` clause of object `B` will import all members -of `A` _except_ the given instance `tc`. Conversely, the second import `import A.given` will import _only_ that given instance. -The two import clauses can also be merged into one: -```scala -object B - import A.{given, _} -``` - -Generally, a normal wildcard selector `_` brings all definitions other than given instances into scope whereas a `given` selector brings all given instances into scope. - -There are two main benefits arising from these rules: - - - It is made clearer where givens in scope are coming from. - In particular, it is not possible to hide imported givens in a long list of regular wildcard imports. - - It enables importing all givens - without importing anything else. This is particularly important since givens - can be anonymous, so the usual recourse of using named imports is not - practical. - -### Importing By Type - -Since givens can be anonymous it is not always practical to import them by their name, and wildcard imports are typically used instead. By-type imports provide a more specific alternative to wildcard imports, which makes it clearer what is imported. Example: - -```scala -import A.{given TC} -``` -This imports any given in `A` that has a type which conforms to `TC`. Importing givens of several types `T1,...,Tn` -is expressed by multiple `given` selectors. -``` -import A.{given T1, ..., given Tn} -``` -Importing all given instances of a parameterized type is expressed by wildcard arguments. -For instance, assuming the object -```scala -object Instances { - given intOrd: Ordering[Int] - given [T: Ordering] listOrd: Ordering[List[T]] - given ec: ExecutionContext = ... - given im: Monoid[Int] -} -``` -the import -```scala -import Instances.{given Ordering[?], given ExecutionContext} -``` -would import the `intOrd`, `listOrd`, and `ec` instances but leave out the `im` instance, since it fits none of the specified bounds. - -By-type imports can be mixed with by-name imports. If both are present in an import clause, by-type imports come last. For instance, the import clause -```scala -import Instances.{im, given Ordering[?]} -``` -would import `im`, `intOrd`, and `listOrd` but leave out `ec`. - -Bounded wildcard selectors also work for normal imports and exports. For instance, consider the following `enum` definition: -```scala -enum Color { - case Red, Green, Blue, Magenta - - def isPrimary(c: Color): Boolean = ... -} -export Color.{_: Color} -``` -The export clause makes all four three `Color` values available as unqualified constants, but -leaves the `isPrimary` method alone. - -### Migration - -The rules for imports stated above have the consequence that a library -would have to migrate in lockstep with all its users from old style implicits and -normal imports to given instances and imports. - -The following modifications avoid this hurdle to migration. - - 1. A `given` import selector also brings old style implicits into scope. So, in Scala 3.0 - an old-style implicit definition can be brought into scope either by a `_` wildcard import or by a `given` import. - - 2. In Scala 3.1, old-style implicits accessed through a `_` wildcard import will give a deprecation warning. - - 3. In some version after 3.1, old-style implicits accessed through a `_` wildcard import will give a compiler error. - -These rules mean that library users can use `given` imports to access old-style implicits in Scala 3.0, -and will be gently nudged and then forced to do so in later versions. Libraries can then switch to -representation clauses once their user base has migrated. - -### Syntax - -``` -Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} -ImportExpr ::= StableId ‘.’ ImportSpec -ImportSpec ::= id - | ‘_’ - | ‘given’ - | ‘{’ ImportSelectors) ‘}’ -ImportSelectors ::= id [‘=>’ id | ‘=>’ ‘_’] [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} -WildCardSelector ::= ‘given’ [InfixType] - | ‘_' [‘:’ InfixType] -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} -``` \ No newline at end of file diff --git a/docs/docs/reference/contextual/import-implied.md b/docs/docs/reference/contextual/import-implied.md index 103872f8c287..d0f0546f4fd4 100644 --- a/docs/docs/reference/contextual/import-implied.md +++ b/docs/docs/reference/contextual/import-implied.md @@ -1 +1 @@ -The contents of this page have [moved](./import-delegate.md). \ No newline at end of file +The contents of this page have [moved](./given-imports.html). \ No newline at end of file diff --git a/docs/docs/reference/contextual/inferable-by-name-parameters.md b/docs/docs/reference/contextual/inferable-by-name-parameters.md index fd5984334404..6ca9bcdd911d 100644 --- a/docs/docs/reference/contextual/inferable-by-name-parameters.md +++ b/docs/docs/reference/contextual/inferable-by-name-parameters.md @@ -1 +1 @@ -The contents of this page have [moved](./by-name-context-parameters.md). +The contents of this page have [moved](./by-name-context-parameters.html). diff --git a/docs/docs/reference/contextual/inferable-params.md b/docs/docs/reference/contextual/inferable-params.md index 4f395ccbdc39..5cacf8421399 100644 --- a/docs/docs/reference/contextual/inferable-params.md +++ b/docs/docs/reference/contextual/inferable-params.md @@ -1 +1 @@ -The contents of this page have [moved](./context-parameters.md). \ No newline at end of file +The contents of this page have [moved](./using-clauses.html). \ No newline at end of file diff --git a/docs/docs/reference/contextual/instance-defs.md b/docs/docs/reference/contextual/instance-defs.md index 8dd75ffb98c3..ebdc46d2c804 100644 --- a/docs/docs/reference/contextual/instance-defs.md +++ b/docs/docs/reference/contextual/instance-defs.md @@ -1 +1 @@ -The contents of this page have [moved](./givens.md). \ No newline at end of file +The contents of this page have [moved](./givens.html). \ No newline at end of file diff --git a/docs/docs/reference/contextual/motivation-new.md b/docs/docs/reference/contextual/motivation-new.md deleted file mode 100644 index db2a695c5480..000000000000 --- a/docs/docs/reference/contextual/motivation-new.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -layout: doc-page -title: "Overview" ---- - -### Critique of the Status Quo - -Scala's implicits are its most distinguished feature. They are _the_ fundamental way to abstract over context. They represent a unified paradigm with a great variety of use cases, among them: implementing type classes, establishing context, dependency injection, expressing capabilities, computing new types and proving relationships between them. - -Following Haskell, Scala was the second popular language to have some form of implicits. Other languages have followed suit. E.g Rust's traits or Swift's protocol extensions. Design proposals are also on the table for Kotlin as [compile time dependency resolution](https://github.com/Kotlin/KEEP/blob/e863b25f8b3f2e9b9aaac361c6ee52be31453ee0/proposals/compile-time-dependency-resolution.md), for C# as [Shapes and Extensions](https://github.com/dotnet/csharplang/issues/164) -or for F# as [Traits](https://github.com/MattWindsor91/visualfsharp/blob/hackathon-vs/examples/fsconcepts.md). Implicits are also a common feature of theorem provers such as Coq or Agda. - -Even though these designs use widely different terminology, they are all variants of the core idea of _term inference_. Given a type, the compiler synthesizes a "canonical" term that has that type. Scala embodies the idea in a purer form than most other languages: An implicit parameter directly leads to an inferred argument term that could also be written down explicitly. By contrast, typeclass based designs are less direct since they hide term inference behind some form of type classification and do not offer the option of writing the inferred quantities (typically, dictionaries) explicitly. - -Given that term inference is where the industry is heading, and given that Scala has it in a very pure form, how come implicits are not more popular? In fact, it's fair to say that implicits are at the same time Scala's most distinguished and most controversial feature. I believe this is due to a number of aspects that together make implicits harder to learn than necessary and also make it harder to prevent abuses. - -Particular criticisms are: - -1. Being very powerful, implicits are easily over-used and mis-used. This observation holds in almost all cases when we talk about _implicit conversions_, which, even though conceptually different, share the same syntax with other implicit definitions. For instance, regarding the two definitions - - ```scala - implicit def i1(implicit x: T): C[T] = ... - implicit def i2(x: T): C[T] = ... - ``` - - the first of these is a conditional implicit _value_, the second an implicit _conversion_. Conditional implicit values are a cornerstone for expressing type classes, whereas most applications of implicit conversions have turned out to be of dubious value. The problem is that many newcomers to the language start with defining implicit conversions since they are easy to understand and seem powerful and convenient. Scala 3 will put under a language flag both definitions and applications of "undisciplined" implicit conversions between types defined elsewhere. This is a useful step to push back against overuse of implicit conversions. But the problem remains that syntactically, conversions and values just look too similar for comfort. - - 2. Another widespread abuse is over-reliance on implicit imports. This often leads to inscrutable type errors that go away with the right import incantation, leaving a feeling of frustration. Conversely, it is hard to see what implicits a program uses since implicits can hide anywhere in a long list of imports. - - 3. The syntax of implicit definitions is too minimal. It consists of a single modifier, `implicit`, that can be attached to a large number of language constructs. A problem with this for newcomers is that it conveys mechanism instead of intent. For instance, a typeclass instance is an implicit object or val if unconditional and an implicit def with implicit parameters referring to some class if conditional. This describes precisely what the implicit definitions translate to -- just drop the `implicit` modifier, and that's it! But the cues that define intent are rather indirect and can be easily misread, as demonstrated by the definitions of `i1` and `i2` above. - - 4. The syntax of implicit parameters also has shortcomings. While implicit _parameters_ are designated specifically, arguments are not. Passing an argument to an implicit parameter looks like a regular application `f(arg)`. This is problematic because it means there can be confusion regarding what parameter gets instantiated in a call. For instance, in - ```scala - def currentMap(implicit ctx: Context): Map[String, Int] - ``` - one cannot write `currentMap("abc")` since the string "abc" is taken as explicit argument to the implicit `ctx` parameter. One has to write `currentMap.apply("abc")` instead, which is awkward and irregular. For the same reason, a method definition can only have one implicit parameter section and it must always come last. This restriction not only reduces orthogonality, but also prevents some useful program constructs, such as a method with a regular parameter whose type depends on an implicit value. Finally, it's also a bit annoying that implicit parameters must have a name, even though in many cases that name is never referenced. - - 5. Implicits pose challenges for tooling. The set of available implicits depends on context, so command completion has to take context into account. This is feasible in an IDE but docs like ScalaDoc that are based static web pages can only provide an approximation. Another problem is that failed implicit searches often give very unspecific error messages, in particular if some deeply recursive implicit search has failed. Note that the Dotty compiler has already made a lot of progress in the error diagnostics area. If a recursive search fails some levels down, it shows what was constructed and what is missing. Also, it suggests imports that can bring missing implicits in scope. - -None of the shortcomings is fatal, after all implicits are very widely used, and many libraries and applications rely on them. But together, they make code using implicits a lot more cumbersome and less clear than it could be. - -Historically, many of these shortcomings come from the way implicits were gradually "discovered" in Scala. Scala originally had only implicit conversions with the intended use case of "extending" a class or trait after it was defined, i.e. what is expressed by implicit classes in later versions of Scala. Implicit parameters and instance definitions came later in 2006 and we picked similar syntax since it seemed convenient. For the same reason, no effort was made to distinguish implicit imports or arguments from normal ones. - -Existing Scala programmers by and large have gotten used to the status quo and see little need for change. But for newcomers this status quo presents a big hurdle. I believe if we want to overcome that hurdle, we should take a step back and allow ourselves to consider a radically new design. - -### The New Design - -The following pages introduce a redesign of contextual abstractions in Scala. They introduce four fundamental changes: - - 1. [Given Instances](./givens.md) are a new way to define basic terms that can be synthesized. They replace implicit definitions. The core principle of the proposal is that, rather than mixing the `implicit` modifier with a large number of features, we have a single way to define terms that can be synthesized for types. - - 2. [Using Clauses](./context-parameters.md) are a new syntax for implicit _parameters_ and their _arguments_. It unambiguously aligns parameters and arguments, solving a number of language warts. It also allows us to have several `using` clauses in a definition. - - 3. ["Given" Imports](./given-imports.md) are a new class of import selectors that specifically import - givens and nothing else. - - 4. [Implicit Conversions](./conversions.md) are now expressed as given instances of a standard `Conversion` class. All other forms of implicit conversions will be phased out. - -This section also contains pages describing other language features that are related to context abstraction. These are: - - - [Context Bounds](./context-bounds.md), which carry over unchanged. - - [Extension Methods](./extension-methods.md) replace implicit classes in a way that integrates better with typeclasses. - - [Implementing Typeclasses](./typeclasses.md) demonstrates how some common typeclasses can be implemented using the new constructs. - - [Typeclass Derivation](./derivation.md) introduces constructs to automatically derive typeclass instances for ADTs. - - [Multiversal Equality](./multiversal-equality.md) introduces a special typeclass to support type safe equality. - - [Context Functions](./context-functions.md) provide a way to abstract over context parameters. - - [By-Name Context Parameters](./by-name-context-parameters.md) are an essential tool to define recursive synthesized values without looping. - - [Relationship with Scala 2 Implicits](./relationship-implicits.md) discusses the relationship between old-style implicits and new-style givens and how to migrate from one to the other. - -Overall, the new design achieves a better separation of term inference from the rest of the language: There is a single way to define givens instead of a multitude of forms all taking an `implicit` modifier. There is a single way to introduce implicit parameters and arguments instead of conflating implicit with normal arguments. There is a separate way to import givens that does not allow them to hide in a sea of normal imports. And there is a single way to define an implicit conversion which is clearly marked as such and does not require special syntax. - -This design thus avoids feature interactions and makes the language more consistent and orthogonal. It will make implicits easier to learn and harder to abuse. It will greatly improve the clarity of the 95% of Scala programs that use implicits. It has thus the potential to fulfil the promise of term inference in a principled way that is also accessible and friendly. - -Could we achieve the same goals by tweaking existing implicits? After having tried for a long time, I believe now that this is impossible. - - - First, some of the problems are clearly syntactic and require different syntax to solve them. - - Second, there is the problem how to migrate. We cannot change the rules in mid-flight. At some stage of language evolution we need to accommodate both the new and the old rules. With a syntax change, this is easy: Introduce the new syntax with new rules, support the old syntax for a while to facilitate cross compilation, deprecate and phase out the old syntax at some later time. Keeping the same syntax does not offer this path, and in fact does not seem to offer any viable path for evolution - - Third, even if we would somehow succeed with migration, we still have the problem - how to teach this. We cannot make existing tutorials go away. Almost all existing tutorials start with implicit conversions, which will go away; they use normal imports, which will go away, and they explain calls to methods with implicit parameters by expanding them to plain applications, which will also go away. This means that we'd have - to add modifications and qualifications to all existing literature and courseware, likely causing more confusion with beginners instead of less. By contrast, with a new syntax there is a clear criterion: Any book or courseware that mentions `implicit` is outdated and should be updated. - diff --git a/docs/docs/reference/contextual/motivation.md b/docs/docs/reference/contextual/motivation.md index 0a1a75e686fd..4f0516e71b77 100644 --- a/docs/docs/reference/contextual/motivation.md +++ b/docs/docs/reference/contextual/motivation.md @@ -3,9 +3,6 @@ layout: doc-page title: "Overview" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./motivation-new.html). - ### Critique of the Status Quo Scala's implicits are its most distinguished feature. They are _the_ fundamental way to abstract over context. They represent a unified paradigm with a great variety of use cases, among them: implementing type classes, establishing context, dependency injection, expressing capabilities, computing new types and proving relationships between them. @@ -38,7 +35,7 @@ Particular criticisms are: ``` one cannot write `currentMap("abc")` since the string "abc" is taken as explicit argument to the implicit `ctx` parameter. One has to write `currentMap.apply("abc")` instead, which is awkward and irregular. For the same reason, a method definition can only have one implicit parameter section and it must always come last. This restriction not only reduces orthogonality, but also prevents some useful program constructs, such as a method with a regular parameter whose type depends on an implicit value. Finally, it's also a bit annoying that implicit parameters must have a name, even though in many cases that name is never referenced. - 5. Implicits pose challenges for tooling. The set of available implicits depends on context, so command completion has to take context into account. This is feasible in an IDE but docs like ScalaDoc that are based static web pages can only provide an approximation. Another problem is that failed implicit searches often give very unspecific error messages, in particular if some deeply recursive implicit search has failed. Note that the Dotty compiler already implements some improvements in this case, but challenges still remain. + 5. Implicits pose challenges for tooling. The set of available implicits depends on context, so command completion has to take context into account. This is feasible in an IDE but docs like ScalaDoc that are based static web pages can only provide an approximation. Another problem is that failed implicit searches often give very unspecific error messages, in particular if some deeply recursive implicit search has failed. Note that the Dotty compiler has already made a lot of progress in the error diagnostics area. If a recursive search fails some levels down, it shows what was constructed and what is missing. Also, it suggests imports that can bring missing implicits in scope. None of the shortcomings is fatal, after all implicits are very widely used, and many libraries and applications rely on them. But together, they make code using implicits a lot more cumbersome and less clear than it could be. @@ -50,11 +47,12 @@ Existing Scala programmers by and large have gotten used to the status quo and s The following pages introduce a redesign of contextual abstractions in Scala. They introduce four fundamental changes: - 1. [Given Instances](./delegates.md) are a new way to define basic terms that can be synthesized. They replace implicit definitions. The core principle of the proposal is that, rather than mixing the `implicit` modifier with a large number of features, we have a single way to define terms that can be synthesized for types. + 1. [Given Instances](./givens.md) are a new way to define basic terms that can be synthesized. They replace implicit definitions. The core principle of the proposal is that, rather than mixing the `implicit` modifier with a large number of features, we have a single way to define terms that can be synthesized for types. - 2. [Given Clauses](./given-clauses.md) are a new syntax for implicit _parameters_ and their _arguments_. Both are introduced with the same keyword, `given`. This unambiguously aligns parameters and arguments, solving a number of language warts. It also allows us to have several implicit parameter sections, and to have implicit parameters followed by normal ones. + 2. [Using Clauses](./using-clauses.md) are a new syntax for implicit _parameters_ and their _arguments_. It unambiguously aligns parameters and arguments, solving a number of language warts. It also allows us to have several `using` clauses in a definition. - 3. [Given Imports](./import-delegate.md) are a new class of imports that specifically import given instances and nothing else. Given instances _must be_ imported with `import given`, a plain import will no longer bring them into scope. + 3. ["Given" Imports](./given-imports.md) are a new class of import selectors that specifically import + givens and nothing else. 4. [Implicit Conversions](./conversions.md) are now expressed as given instances of a standard `Conversion` class. All other forms of implicit conversions will be phased out. @@ -65,11 +63,11 @@ This section also contains pages describing other language features that are rel - [Implementing Typeclasses](./typeclasses.md) demonstrates how some common typeclasses can be implemented using the new constructs. - [Typeclass Derivation](./derivation.md) introduces constructs to automatically derive typeclass instances for ADTs. - [Multiversal Equality](./multiversal-equality.md) introduces a special typeclass to support type safe equality. - - [Implicit Function Types](./implicit-function-types.md) provide a way to abstract over given clauses. - - [Implicit By-Name Parameters](./implicit-by-name-parameters.md) are an essential tool to define recursive synthesized values without looping. + - [Context Functions](./context-functions.md) provide a way to abstract over context parameters. + - [By-Name Context Parameters](./by-name-context-parameters.md) are an essential tool to define recursive synthesized values without looping. - [Relationship with Scala 2 Implicits](./relationship-implicits.md) discusses the relationship between old-style implicits and new-style givens and how to migrate from one to the other. -Overall, the new design achieves a better separation of term inference from the rest of the language: There is a single way to define given instances instead of a multitude of forms all taking an `implicit` modifier. There is a single way to introduce implicit parameters and arguments instead of conflating implicit with normal arguments. There is a separate way to import given instances that does not allow them to hide in a sea of normal imports. And there is a single way to define an implicit conversion which is clearly marked as such and does not require special syntax. +Overall, the new design achieves a better separation of term inference from the rest of the language: There is a single way to define givens instead of a multitude of forms all taking an `implicit` modifier. There is a single way to introduce implicit parameters and arguments instead of conflating implicit with normal arguments. There is a separate way to import givens that does not allow them to hide in a sea of normal imports. And there is a single way to define an implicit conversion which is clearly marked as such and does not require special syntax. This design thus avoids feature interactions and makes the language more consistent and orthogonal. It will make implicits easier to learn and harder to abuse. It will greatly improve the clarity of the 95% of Scala programs that use implicits. It has thus the potential to fulfil the promise of term inference in a principled way that is also accessible and friendly. diff --git a/docs/docs/reference/contextual/multiversal-equality-new.md b/docs/docs/reference/contextual/multiversal-equality-new.md deleted file mode 100644 index 39ffac6de5e0..000000000000 --- a/docs/docs/reference/contextual/multiversal-equality-new.md +++ /dev/null @@ -1,213 +0,0 @@ ---- -layout: doc-page -title: "Multiversal Equality" ---- - -Previously, Scala had universal equality: Two values of any types -could be compared with each other with `==` and `!=`. This came from -the fact that `==` and `!=` are implemented in terms of Java's -`equals` method, which can also compare values of any two reference -types. - -Universal equality is convenient. But it is also dangerous since it -undermines type safety. For instance, let's assume one is left after some refactoring -with an erroneous program where a value `y` has type `S` instead of the correct type `T`. - -```scala -val x = ... // of type T -val y = ... // of type S, but should be T -x == y // typechecks, will always yield false -``` - -If `y` gets compared to other values of type `T`, -the program will still typecheck, since values of all types can be compared with each other. -But it will probably give unexpected results and fail at runtime. - -Multiversal equality is an opt-in way to make universal equality -safer. It uses a binary typeclass `Eql` to indicate that values of -two given types can be compared with each other. -The example above would not typecheck if `S` or `T` was a class -that derives `Eql`, e.g. -```scala -class T derives Eql -``` -Alternatively, one can also provide an `Eql` given instance directly, like this: -```scala -given Eql[T, T] = Eql.derived -``` -This definition effectively says that values of type `T` can (only) be -compared to other values of type `T` when using `==` or `!=`. The definition -affects type checking but it has no significance for runtime -behavior, since `==` always maps to `equals` and `!=` always maps to -the negation of `equals`. The right hand side `Eql.derived` of the definition -is a value that has any `Eql` instance as its type. Here is the definition of class -`Eql` and its companion object: -```scala -package scala -import annotation.implicitNotFound - -@implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") -sealed trait Eql[-L, -R] - -object Eql { - object derived extends Eql[Any, Any] -} -``` - -One can have several `Eql` given instances for a type. For example, the four -definitions below make values of type `A` and type `B` comparable with -each other, but not comparable to anything else: - -```scala -given Eql[A, A] = Eql.derived -given Eql[B, B] = Eql.derived -given Eql[A, B] = Eql.derived -given Eql[B, A] = Eql.derived -``` -The `scala.Eql` object defines a number of `Eql` given instances that together -define a rule book for what standard types can be compared (more details below). - -There's also a "fallback" instance named `eqlAny` that allows comparisons -over all types that do not themselves have an `Eql` given. `eqlAny` is defined as follows: - -```scala -def eqlAny[L, R]: Eql[L, R] = Eql.derived -``` - -Even though `eqlAny` is not declared a given, the compiler will still construct an `eqlAny` instance as answer to an implicit search for the -type `Eql[L, R]`, unless `L` or `R` have `Eql` instances -defined on them, or the language feature `strictEquality` is enabled - -The primary motivation for having `eqlAny` is backwards compatibility, -if this is of no concern, one can disable `eqlAny` by enabling the language -feature `strictEquality`. As for all language features this can be either -done with an import - -```scala -import scala.language.strictEquality -``` -or with a command line option `-language:strictEquality`. - -## Deriving Eql Instances - -Instead of defining `Eql` instances directly, it is often more convenient to derive them. Example: -```scala -class Box[T](x: T) derives Eql -``` -By the usual rules of [typeclass derivation](./derivation.md), -this generates the following `Eql` instance in the companion object of `Box`: -```scala -given [T, U](using Eql[T, U]) as Eql[Box[T], Box[U]] = Eql.derived -``` -That is, two boxes are comparable with `==` or `!=` if their elements are. Examples: -```scala -new Box(1) == new Box(1L) // ok since there is an instance for `Eql[Int, Long]` -new Box(1) == new Box("a") // error: can't compare -new Box(1) == 1 // error: can't compare -``` - -## Precise Rules for Equality Checking - -The precise rules for equality checking are as follows. - -If the `strictEquality` feature is enabled then -a comparison using `x == y` or `x != y` between values `x: T` and `y: U` -is legal if there is a given of type `Eql[T, U]`. - -In the default case where the `strictEquality` feature is not enabled the comparison is -also legal if - - 1. `T` and `U` are the same, or - 2. one of `T`, `U` is a subtype of the _lifted_ version of the other type, or - 3. neither `T` nor `U` have a _reflexive_ `Eql` instance. - -Explanations: - - - _lifting_ a type `S` means replacing all references to abstract types - in covariant positions of `S` by their upper bound, and to replacing - all refinement types in covariant positions of `S` by their parent. - - a type `T` has a _reflexive_ `Eql` instance if the implicit search for `Eql[T, T]` - succeeds. - -## Predefined Eql Instances - -The `Eql` object defines instances for comparing - - the primitive types `Byte`, `Short`, `Char`, `Int`, `Long`, `Float`, `Double`, `Boolean`, and `Unit`, - - `java.lang.Number`, `java.lang.Boolean`, and `java.lang.Character`, - - `scala.collection.Seq`, and `scala.collection.Set`. - -Instances are defined so that every one of these types has a _reflexive_ `Eql` instance, and the following holds: - - - Primitive numeric types can be compared with each other. - - Primitive numeric types can be compared with subtypes of `java.lang.Number` (and _vice versa_). - - `Boolean` can be compared with `java.lang.Boolean` (and _vice versa_). - - `Char` can be compared with `java.lang.Character` (and _vice versa_). - - Two sequences (of arbitrary subtypes of `scala.collection.Seq`) can be compared - with each other if their element types can be compared. The two sequence types - need not be the same. - - Two sets (of arbitrary subtypes of `scala.collection.Set`) can be compared - with each other if their element types can be compared. The two set types - need not be the same. - - Any subtype of `AnyRef` can be compared with `Null` (and _vice versa_). - -## Why Two Type Parameters? - -One particular feature of the `Eql` type is that it takes _two_ type parameters, representing the types of the two items to be compared. By contrast, conventional -implementations of an equality type class take only a single type parameter which represents the common type of _both_ operands. One type parameter is simpler than two, so why go through the additional complication? The reason has to do with the fact that, rather than coming up with a type class where no operation existed before, -we are dealing with a refinement of pre-existing, universal equality. It's best illustrated through an example. - -Say you want to come up with a safe version of the `contains` method on `List[T]`. The original definition of `contains` in the standard library was: -```scala -class List[+T] { - ... - def contains(x: Any): Boolean -} -``` -That uses universal equality in an unsafe way since it permits arguments of any type to be compared with the list's elements. The "obvious" alternative definition -```scala - def contains(x: T): Boolean -``` -does not work, since it refers to the covariant parameter `T` in a nonvariant context. The only variance-correct way to use the type parameter `T` in `contains` is as a lower bound: -```scala - def contains[U >: T](x: U): Boolean -``` -This generic version of `contains` is the one used in the current (Scala 2.13) version of `List`. -It looks different but it admits exactly the same applications as the `contains(x: Any)` definition we started with. -However, we can make it more useful (i.e. restrictive) by adding an `Eql` parameter: -```scala - def contains[U >: T](x: U)(using Eql[T, U]): Boolean // (1) -``` -This version of `contains` is equality-safe! More precisely, given -`x: T`, `xs: List[T]` and `y: U`, then `xs.contains(y)` is type-correct if and only if -`x == y` is type-correct. - -Unfortunately, the crucial ability to "lift" equality type checking from simple equality and pattern matching to arbitrary user-defined operations gets lost if we restrict ourselves to an equality class with a single type parameter. Consider the following signature of `contains` with a hypothetical `Eql1[T]` type class: -```scala - def contains[U >: T](x: U)(using Eql1[U]): Boolean // (2) -``` -This version could be applied just as widely as the original `contains(x: Any)` method, -since the `Eql1[Any]` fallback is always available! So we have gained nothing. What got lost in the transition to a single parameter type class was the original rule that `Eql[A, B]` is available only if neither `A` nor `B` have a reflexive `Eql` instance. That rule simply cannot be expressed if there is a single type parameter for `Eql`. - -The situation is different under `-language:strictEquality`. In that case, -the `Eql[Any, Any]` or `Eql1[Any]` instances would never be available, and the -single and two-parameter versions would indeed coincide for most practical purposes. - -But assuming `-language:strictEquality` immediately and everywhere poses migration problems which might well be unsurmountable. Consider again `contains`, which is in the standard library. Parameterizing it with the `Eql` type class as in (1) is an immediate win since it rules out non-sensical applications while still allowing all sensible ones. -So it can be done almost at any time, modulo binary compatibility concerns. -On the other hand, parameterizing `contains` with `Eql1` as in (2) would make `contains` -unusable for all types that have not yet declared an `Eql1` instance, including all -types coming from Java. This is clearly unacceptable. It would lead to a situation where, -rather than migrating existing libraries to use safe equality, the only upgrade path is to have parallel libraries, with the new version only catering to types deriving `Eql1` and the old version dealing with everything else. Such a split of the ecosystem would be very problematic, which means the cure is likely to be worse than the disease. - -For these reasons, it looks like a two-parameter type class is the only way forward because it can take the existing ecosystem where it is and migrate it towards a future where more and more code uses safe equality. - -In applications where `-language:strictEquality` is the default one could also introduce a one-parameter type alias such as -```scala -type Eq[-T] = Eql[T, T] -``` -Operations needing safe equality could then use this alias instead of the two-parameter `Eql` class. But it would only -work under `-language:strictEquality`, since otherwise the universal `Eq[Any]` instance would be available everywhere. - - -More on multiversal equality is found in a [blog post](http://www.scala-lang.org/blog/2016/05/06/multiversal-equality.html) -and a [Github issue](https://github.com/lampepfl/dotty/issues/1247). diff --git a/docs/docs/reference/contextual/multiversal-equality.md b/docs/docs/reference/contextual/multiversal-equality.md index c0417f4e24d7..39ffac6de5e0 100644 --- a/docs/docs/reference/contextual/multiversal-equality.md +++ b/docs/docs/reference/contextual/multiversal-equality.md @@ -2,8 +2,6 @@ layout: doc-page title: "Multiversal Equality" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./multiversal-equality-new.html). Previously, Scala had universal equality: Two values of any types could be compared with each other with `==` and `!=`. This came from @@ -56,7 +54,7 @@ object Eql { } ``` -One can have several `Eql` givens for a type. For example, the four +One can have several `Eql` given instances for a type. For example, the four definitions below make values of type `A` and type `B` comparable with each other, but not comparable to anything else: @@ -66,7 +64,7 @@ given Eql[B, B] = Eql.derived given Eql[A, B] = Eql.derived given Eql[B, A] = Eql.derived ``` -The `scala.Eql` object defines a number of `Eql` givens that together +The `scala.Eql` object defines a number of `Eql` given instances that together define a rule book for what standard types can be compared (more details below). There's also a "fallback" instance named `eqlAny` that allows comparisons @@ -76,8 +74,8 @@ over all types that do not themselves have an `Eql` given. `eqlAny` is defined def eqlAny[L, R]: Eql[L, R] = Eql.derived ``` -Even though `eqlAny` is not declared a given instance, the compiler will still construct an `eqlAny` instance as answer to an implicit search for the -type `Eql[L, R]`, unless `L` or `R` have `Eql` given instances +Even though `eqlAny` is not declared a given, the compiler will still construct an `eqlAny` instance as answer to an implicit search for the +type `Eql[L, R]`, unless `L` or `R` have `Eql` instances defined on them, or the language feature `strictEquality` is enabled The primary motivation for having `eqlAny` is backwards compatibility, @@ -99,7 +97,7 @@ class Box[T](x: T) derives Eql By the usual rules of [typeclass derivation](./derivation.md), this generates the following `Eql` instance in the companion object of `Box`: ```scala -given [T, U](given Eql[T, U]) : Eql[Box[T], Box[U]] = Eql.derived +given [T, U](using Eql[T, U]) as Eql[Box[T], Box[U]] = Eql.derived ``` That is, two boxes are comparable with `==` or `!=` if their elements are. Examples: ```scala @@ -114,31 +112,31 @@ The precise rules for equality checking are as follows. If the `strictEquality` feature is enabled then a comparison using `x == y` or `x != y` between values `x: T` and `y: U` -is legal if there is a given instance for `Eql[T, U]`. +is legal if there is a given of type `Eql[T, U]`. In the default case where the `strictEquality` feature is not enabled the comparison is also legal if 1. `T` and `U` are the same, or 2. one of `T`, `U` is a subtype of the _lifted_ version of the other type, or - 3. neither `T` nor `U` have a _reflexive_ `Eql` given. + 3. neither `T` nor `U` have a _reflexive_ `Eql` instance. Explanations: - _lifting_ a type `S` means replacing all references to abstract types in covariant positions of `S` by their upper bound, and to replacing all refinement types in covariant positions of `S` by their parent. - - a type `T` has a _reflexive_ `Eql` given if the implicit search for `Eql[T, T]` + - a type `T` has a _reflexive_ `Eql` instance if the implicit search for `Eql[T, T]` succeeds. ## Predefined Eql Instances -The `Eql` object defines givens for comparing +The `Eql` object defines instances for comparing - the primitive types `Byte`, `Short`, `Char`, `Int`, `Long`, `Float`, `Double`, `Boolean`, and `Unit`, - `java.lang.Number`, `java.lang.Boolean`, and `java.lang.Character`, - `scala.collection.Seq`, and `scala.collection.Set`. -Given instances are defined so that every one of these types has a _reflexive_ `Eql` given, and the following holds: +Instances are defined so that every one of these types has a _reflexive_ `Eql` instance, and the following holds: - Primitive numeric types can be compared with each other. - Primitive numeric types can be compared with subtypes of `java.lang.Number` (and _vice versa_). @@ -177,7 +175,7 @@ This generic version of `contains` is the one used in the current (Scala 2.13) v It looks different but it admits exactly the same applications as the `contains(x: Any)` definition we started with. However, we can make it more useful (i.e. restrictive) by adding an `Eql` parameter: ```scala - def contains[U >: T](x: U)(given Eql[T, U]): Boolean // (1) + def contains[U >: T](x: U)(using Eql[T, U]): Boolean // (1) ``` This version of `contains` is equality-safe! More precisely, given `x: T`, `xs: List[T]` and `y: U`, then `xs.contains(y)` is type-correct if and only if @@ -185,10 +183,10 @@ This version of `contains` is equality-safe! More precisely, given Unfortunately, the crucial ability to "lift" equality type checking from simple equality and pattern matching to arbitrary user-defined operations gets lost if we restrict ourselves to an equality class with a single type parameter. Consider the following signature of `contains` with a hypothetical `Eql1[T]` type class: ```scala - def contains[U >: T](x: U)(given Eql1[U]): Boolean // (2) + def contains[U >: T](x: U)(using Eql1[U]): Boolean // (2) ``` This version could be applied just as widely as the original `contains(x: Any)` method, -since the `Eql1[Any]` fallback is always available! So we have gained nothing. What got lost in the transition to a single parameter type class was the original rule that `Eql[A, B]` is available only if neither `A` nor `B` have a reflexive `Eql` given. That rule simply cannot be expressed if there is a single type parameter for `Eql`. +since the `Eql1[Any]` fallback is always available! So we have gained nothing. What got lost in the transition to a single parameter type class was the original rule that `Eql[A, B]` is available only if neither `A` nor `B` have a reflexive `Eql` instance. That rule simply cannot be expressed if there is a single type parameter for `Eql`. The situation is different under `-language:strictEquality`. In that case, the `Eql[Any, Any]` or `Eql1[Any]` instances would never be available, and the @@ -197,7 +195,7 @@ single and two-parameter versions would indeed coincide for most practical purpo But assuming `-language:strictEquality` immediately and everywhere poses migration problems which might well be unsurmountable. Consider again `contains`, which is in the standard library. Parameterizing it with the `Eql` type class as in (1) is an immediate win since it rules out non-sensical applications while still allowing all sensible ones. So it can be done almost at any time, modulo binary compatibility concerns. On the other hand, parameterizing `contains` with `Eql1` as in (2) would make `contains` -unusable for all types that have not yet declared an `Eql1` given, including all +unusable for all types that have not yet declared an `Eql1` instance, including all types coming from Java. This is clearly unacceptable. It would lead to a situation where, rather than migrating existing libraries to use safe equality, the only upgrade path is to have parallel libraries, with the new version only catering to types deriving `Eql1` and the old version dealing with everything else. Such a split of the ecosystem would be very problematic, which means the cure is likely to be worse than the disease. diff --git a/docs/docs/reference/contextual/query-types-spec.md b/docs/docs/reference/contextual/query-types-spec.md index 2dc89a97b62e..aebe0320ff4f 100644 --- a/docs/docs/reference/contextual/query-types-spec.md +++ b/docs/docs/reference/contextual/query-types-spec.md @@ -1 +1 @@ -The contents of this page have [moved](./context-functions-spec.md). \ No newline at end of file +The contents of this page have [moved](./context-functions-spec.html). \ No newline at end of file diff --git a/docs/docs/reference/contextual/query-types.md b/docs/docs/reference/contextual/query-types.md index 43b8d6c60754..6b6fe7ecb89e 100644 --- a/docs/docs/reference/contextual/query-types.md +++ b/docs/docs/reference/contextual/query-types.md @@ -1 +1 @@ -The contents of this page have [moved](./context-functions.md). \ No newline at end of file +The contents of this page have [moved](./context-functions.html). \ No newline at end of file diff --git a/docs/docs/reference/contextual/relationship-implicits-new.md b/docs/docs/reference/contextual/relationship-implicits-new.md deleted file mode 100644 index 16b0a010de28..000000000000 --- a/docs/docs/reference/contextual/relationship-implicits-new.md +++ /dev/null @@ -1,189 +0,0 @@ ---- -layout: doc-page -title: Relationship with Scala 2 Implicits ---- - -Many, but not all, of the new contextual abstraction features in Scala 3 can be mapped to Scala 2's implicits. This page gives a rundown on the relationships between new and old features. - -## Simulating Scala 3 Contextual Abstraction Concepts with Scala 2 Implicits - -### Given Instances - -Given instances can be mapped to combinations of implicit objects, classes and implicit methods. - - 1. Given instances without parameters are mapped to implicit objects. E.g., - ```scala - given intOrd as Ord[Int] { ... } - ``` - maps to - ```scala - implicit object IntOrd extends Ord[Int] { ... } - ``` - 2. Parameterized givens are mapped to combinations of classes and implicit methods. E.g., - ```scala - given listOrd[T](using ord: Ord[T]) as Ord[List[T]] { ... } - ``` - maps to - ```scala - class ListOrd[T](implicit ord: Ord[T]) extends Ord[List[T]] { ... } - final implicit def ListOrd[T](implicit ord: Ord[T]): ListOrd[T] = new ListOrd[T] - ``` - 3. Alias givens map to implicit methods or implicit lazy vals. If an alias has neither type nor context parameters, - it is treated as a lazy val, unless the right hand side is a simple reference, in which case we can use a forwarder to - that reference without caching it. - -Examples: -```scala -given global as ExecutionContext = new ForkJoinContext() - -val ctx: Context -given Context = ctx -``` -would map to -```scala -final implicit lazy val global: ExecutionContext = new ForkJoinContext() -final implicit def given_Context = ctx -``` - -### Anonymous Givens - -Anonymous givens get compiler synthesized names, which are generated in a reproducible way from the implemented type(s). For example, if the names of the `IntOrd` and `ListOrd` givens above were left out, the following names would be synthesized instead: -```scala -given given_Ord_Int as Ord[Int] { ... } -given given_Ord_List[T] as Ord[List[T]] { ... } -``` -The synthesized type names are formed from - - - the prefix `given_`, - - the simple name(s) of the implemented type(s), leaving out any prefixes, - - the simple name(s) of the toplevel argument type constructors to these types. - -Tuples are treated as transparent, i.e. a type `F[(X, Y)]` would get the synthesized name -`F_X_Y`. Directly implemented function types `A => B` are represented as `A_to_B`. Function types used as arguments to other type constructors are represented as `Function`. - -### Anonymous Collective Extensions - -Anonymous collective extensions also get compiler synthesized names, which are formed from - - - the prefix `extension_` - - the name of the first defined extension method - - the simple name of the first parameter type of this extension method - - the simple name(s) of the toplevel argument type constructors to this type. - -For example, the extension -```scala -extension on [T] (xs: List[T]) { - def second = ... -} -``` -gets the synthesized name `extension_second_List_T`. - -### Given Clauses - -Given clauses correspond largely to Scala-2's implicit parameter clauses. E.g. -```scala -def max[T](x: T, y: T)(using ord: Ord[T]): T -``` -would be written -```scala -def max[T](x: T, y: T)(implicit ord: Ord[T]): T -``` -in Scala 2. The main difference concerns applications of such parameters. -Explicit arguments to parameters of using clauses _must_ be written using `(using ...)`, -mirroring the definition syntax. E.g, `max(2, 3)(using IntOrd)`. -Scala 2 uses normal applications `max(2, 3)(IntOrd)` instead. The Scala 2 syntax has some inherent ambiguities and restrictions which are overcome by the new syntax. For instance, multiple implicit parameter lists are not available in the old syntax, even though they can be simulated using auxiliary objects in the "Aux" pattern. - -The `summon` method corresponds to `implicitly` in Scala 2. -It is precisely the same as the `the` method in Shapeless. -The difference between `summon` (or `the`) and `implicitly` is -that `summon` can return a more precise type than the type that was -asked for. - -### Context Bounds - -Context bounds are the same in both language versions. They expand to the respective forms of implicit parameters. - -**Note:** To ease migration, context bounds in Dotty map for a limited time to old-style implicit parameters for which arguments can be passed either in a using clause or -in a normal argument list. Once old-style implicits are deprecated, context bounds -will map to with clauses instead. - -### Extension Methods - -Extension methods have no direct counterpart in Scala 2, but they can be simulated with implicit classes. For instance, the extension method -```scala -def (c: Circle).circumference: Double = c.radius * math.Pi * 2 -``` -could be simulated to some degree by -```scala -implicit class CircleDecorator(c: Circle) extends AnyVal { - def circumference: Double = c.radius * math.Pi * 2 -} -``` -Abstract extension methods in traits that are implemented in given instances have no direct counterpart in Scala-2. The only way to simulate these is to make implicit classes available through imports. The Simulacrum macro library can automate this process in some cases. - -### Typeclass Derivation - -Typeclass derivation has no direct counterpart in the Scala 2 language. Comparable functionality can be achieved by macro-based libraries such as Shapeless, Magnolia, or scalaz-deriving. - -### Implicit Function Types - -Implicit function types have no analogue in Scala 2. - -### Implicit By-Name Parameters - -Implicit by-name parameters are not supported in Scala 2, but can be emulated to some degree by the `Lazy` type in Shapeless. - -## Simulating Scala 2 Implicits in Scala 3 - -### Implicit Conversions - -Implicit conversion methods in Scala 2 can be expressed as given instances of the `scala.Conversion` class in Dotty. E.g. instead of -```scala -implicit def stringToToken(str: String): Token = new Keyword(str) -``` -one can write -```scala -given stringToToken as Conversion[String, Token] { - def apply(str: String): Token = KeyWord(str) -} -``` -or -```scala -given stringToToken as Conversion[String, Token] = KeyWord(_) -``` - -### Implicit Classes - -Implicit classes in Scala 2 are often used to define extension methods, which are directly supported in Dotty. Other uses of implicit classes can be simulated by a pair of a regular class and a given `Conversion` instance. - -### Implicit Values - -Implicit `val` definitions in Scala 2 can be expressed in Dotty using a regular `val` definition and an alias given. -E.g., Scala 2's -```scala -lazy implicit val pos: Position = tree.sourcePos -``` -can be expressed in Dotty as -```scala -lazy val pos: Position = tree.sourcePos -given Position = pos -``` - -### Abstract Implicits - -An abstract implicit `val` or `def` in Scala 2 can be expressed in Dotty using a regular abstract definition and an alias given. E.g., Scala 2's -```scala -implicit def symDecorator: SymDecorator -``` -can be expressed in Dotty as -```scala -def symDecorator: SymDecorator -given SymDecorator = symDecorator -``` - -## Implementation Status and Timeline - -The Dotty implementation implements both Scala-2's implicits and the new abstractions. In fact, support for Scala-2's implicits is an essential part of the common language subset between 2.13/2.14 and Dotty. -Migration to the new abstractions will be supported by making automatic rewritings available. - -Depending on adoption patterns, old style implicits might start to be deprecated in a version following Scala 3.0. diff --git a/docs/docs/reference/contextual/relationship-implicits.md b/docs/docs/reference/contextual/relationship-implicits.md index eab209106416..e2fba69bec65 100644 --- a/docs/docs/reference/contextual/relationship-implicits.md +++ b/docs/docs/reference/contextual/relationship-implicits.md @@ -3,12 +3,9 @@ layout: doc-page title: Relationship with Scala 2 Implicits --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./relationship-implicits-new.html). - Many, but not all, of the new contextual abstraction features in Scala 3 can be mapped to Scala 2's implicits. This page gives a rundown on the relationships between new and old features. -## Simulating Contextual Abstraction with Implicits +## Simulating Scala 3 Contextual Abstraction Concepts with Scala 2 Implicits ### Given Instances @@ -16,28 +13,28 @@ Given instances can be mapped to combinations of implicit objects, classes and i 1. Given instances without parameters are mapped to implicit objects. E.g., ```scala - given intOrd: Ord[Int] { ... } + given intOrd as Ord[Int] { ... } ``` maps to ```scala implicit object IntOrd extends Ord[Int] { ... } ``` - 2. Parameterized given instances are mapped to combinations of classes and implicit methods. E.g., + 2. Parameterized givens are mapped to combinations of classes and implicit methods. E.g., ```scala - given listOrd[T](given ord: Ord[T]): Ord[List[T]] { ... } + given listOrd[T](using ord: Ord[T]) as Ord[List[T]] { ... } ``` maps to ```scala class ListOrd[T](implicit ord: Ord[T]) extends Ord[List[T]] { ... } final implicit def ListOrd[T](implicit ord: Ord[T]): ListOrd[T] = new ListOrd[T] ``` - 3. Alias givens map to implicit methods or implicit lazy vals. If an alias has neither type parameters nor a given clause, + 3. Alias givens map to implicit methods or implicit lazy vals. If an alias has neither type nor context parameters, it is treated as a lazy val, unless the right hand side is a simple reference, in which case we can use a forwarder to that reference without caching it. Examples: ```scala -given global: ExecutionContext = new ForkJoinContext() +given global as ExecutionContext = new ForkJoinContext() val ctx: Context given Context = ctx @@ -52,8 +49,8 @@ final implicit def given_Context = ctx Anonymous given instances get compiler synthesized names, which are generated in a reproducible way from the implemented type(s). For example, if the names of the `IntOrd` and `ListOrd` givens above were left out, the following names would be synthesized instead: ```scala -given given_Ord_Int : Ord[Int] { ... } -given given_Ord_List[T] : Ord[List[T]] { ... } +given given_Ord_Int as Ord[Int] { ... } +given given_Ord_List[T] as Ord[List[T]] { ... } ``` The synthesized type names are formed from @@ -81,20 +78,19 @@ extension on [T] (xs: List[T]) { ``` gets the synthesized name `extension_second_List_T`. - ### Given Clauses -Given clauses corresponds largely to Scala-2's implicit parameter clauses. E.g. +Given clauses correspond largely to Scala-2's implicit parameter clauses. E.g. ```scala -def max[T](x: T, y: T)(given ord: Ord[T]): T +def max[T](x: T, y: T)(using ord: Ord[T]): T ``` would be written ```scala def max[T](x: T, y: T)(implicit ord: Ord[T]): T ``` in Scala 2. The main difference concerns applications of such parameters. -Explicit arguments to parameters of given clauses _must_ be written using `given`, -mirroring the definition syntax. E.g, `max(2, 3)(given IntOrd`). +Explicit arguments to parameters of using clauses _must_ be written using `(using ...)`, +mirroring the definition syntax. E.g, `max(2, 3)(using IntOrd)`. Scala 2 uses normal applications `max(2, 3)(IntOrd)` instead. The Scala 2 syntax has some inherent ambiguities and restrictions which are overcome by the new syntax. For instance, multiple implicit parameter lists are not available in the old syntax, even though they can be simulated using auxiliary objects in the "Aux" pattern. The `summon` method corresponds to `implicitly` in Scala 2. @@ -105,9 +101,11 @@ asked for. ### Context Bounds -Context bounds are the same in both language versions. -They expand to `implicit` parameters in Scala 2 and also in Scala 3.0. -They will expand to context parameters from Scala 3.1 on. +Context bounds are the same in both language versions. They expand to the respective forms of implicit parameters. + +**Note:** To ease migration, context bounds in Dotty map for a limited time to old-style implicit parameters for which arguments can be passed either in a using clause or +in a normal argument list. Once old-style implicits are deprecated, context bounds +will map to with clauses instead. ### Extension Methods @@ -121,7 +119,7 @@ implicit class CircleDecorator(c: Circle) extends AnyVal { def circumference: Double = c.radius * math.Pi * 2 } ``` -Extension methods in given instances have no direct counterpart in Scala-2. The only way to simulate these is to make implicit classes available through imports. The Simulacrum macro library can automate this process in some cases. +Abstract extension methods in traits that are implemented in given instances have no direct counterpart in Scala-2. The only way to simulate these is to make implicit classes available through imports. The Simulacrum macro library can automate this process in some cases. ### Typeclass Derivation @@ -135,7 +133,7 @@ Implicit function types have no analogue in Scala 2. Implicit by-name parameters are not supported in Scala 2, but can be emulated to some degree by the `Lazy` type in Shapeless. -## Simulating Scala 2 Implicits in Dotty +## Simulating Scala 2 Implicits in Scala 3 ### Implicit Conversions @@ -145,14 +143,18 @@ implicit def stringToToken(str: String): Token = new Keyword(str) ``` one can write ```scala -given stringToToken: Conversion[String, Token] { - def apply(str: String): Token = new KeyWord(str) +given stringToToken as Conversion[String, Token] { + def apply(str: String): Token = KeyWord(str) } ``` +or +```scala +given stringToToken as Conversion[String, Token] = KeyWord(_) +``` ### Implicit Classes -Implicit classes in Scala 2 are often used to define extension methods, which are directly supported in Dotty. Other uses of implicit classes can be simulated by a pair of a regular class and a given instance of `Conversion` type. +Implicit classes in Scala 2 are often used to define extension methods, which are directly supported in Dotty. Other uses of implicit classes can be simulated by a pair of a regular class and a given `Conversion` instance. ### Implicit Values diff --git a/docs/docs/reference/contextual/typeclasses-new.md b/docs/docs/reference/contextual/typeclasses-new.md deleted file mode 100644 index 8175cea64e93..000000000000 --- a/docs/docs/reference/contextual/typeclasses-new.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -layout: doc-page -title: "Implementing Typeclasses" ---- - -Given instances, extension methods and context bounds -allow a concise and natural expression of _typeclasses_. Typeclasses are just traits -with canonical implementations defined by given instances. Here are some examples of standard typeclasses: - -### Semigroups and monoids: - -```scala -trait SemiGroup[T] { - @infix def (x: T) combine (y: T): T -} - -trait Monoid[T] extends SemiGroup[T] { - def unit: T -} - -object Monoid { - def apply[T](using m: Monoid[T]) = m -} - -given Monoid[String] { - def (x: String) combine (y: String): String = x.concat(y) - def unit: String = "" -} - -given Monoid[Int] { - def (x: Int) combine (y: Int): Int = x + y - def unit: Int = 0 -} - -def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(Monoid[T].unit)(_ combine _) -``` - -### Functors and monads: - -```scala -trait Functor[F[_]] { - def [A, B](x: F[A]).map(f: A => B): F[B] -} - -trait Monad[F[_]] extends Functor[F] { - def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B] - def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure) - - def pure[A](x: A): F[A] -} - -given listMonad as Monad[List] { - def [A, B](xs: List[A]).flatMap(f: A => List[B]): List[B] = - xs.flatMap(f) - def pure[A](x: A): List[A] = - List(x) -} - -given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { - def [A, B](r: Ctx => A).flatMap(f: A => Ctx => B): Ctx => B = - ctx => f(r(ctx))(ctx) - def pure[A](x: A): Ctx => A = - ctx => x -} -``` diff --git a/docs/docs/reference/contextual/typeclasses.md b/docs/docs/reference/contextual/typeclasses.md index 9afa47a04b45..8175cea64e93 100644 --- a/docs/docs/reference/contextual/typeclasses.md +++ b/docs/docs/reference/contextual/typeclasses.md @@ -3,10 +3,6 @@ layout: doc-page title: "Implementing Typeclasses" --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./typeclasses-new.html). - - Given instances, extension methods and context bounds allow a concise and natural expression of _typeclasses_. Typeclasses are just traits with canonical implementations defined by given instances. Here are some examples of standard typeclasses: @@ -14,50 +10,55 @@ with canonical implementations defined by given instances. Here are some example ### Semigroups and monoids: ```scala -trait SemiGroup[T] with - def (x: T).combine(y: T): T +trait SemiGroup[T] { + @infix def (x: T) combine (y: T): T +} -trait Monoid[T] extends SemiGroup[T] with +trait Monoid[T] extends SemiGroup[T] { def unit: T +} -object Monoid with - def apply[T](given Monoid[T]) = summon[Monoid[T]] +object Monoid { + def apply[T](using m: Monoid[T]) = m +} -given Monoid[String] with - def (x: String).combine(y: String): String = x.concat(y) +given Monoid[String] { + def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" +} -given Monoid[Int] with - def (x: Int).combine(y: Int): Int = x + y +given Monoid[Int] { + def (x: Int) combine (y: Int): Int = x + y def unit: Int = 0 +} def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(Monoid[T].unit)(_.combine(_)) + xs.foldLeft(Monoid[T].unit)(_ combine _) ``` ### Functors and monads: ```scala trait Functor[F[_]] { - def [A, B](x: F[A]) map (f: A => B): F[B] + def [A, B](x: F[A]).map(f: A => B): F[B] } trait Monad[F[_]] extends Functor[F] { - def [A, B](x: F[A]) flatMap (f: A => F[B]): F[B] - def [A, B](x: F[A]) map (f: A => B) = x.flatMap(f `andThen` pure) + def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B] + def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure) def pure[A](x: A): F[A] } -given listMonad: Monad[List] { - def [A, B](xs: List[A]) flatMap (f: A => List[B]): List[B] = +given listMonad as Monad[List] { + def [A, B](xs: List[A]).flatMap(f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } -given readerMonad[Ctx]: Monad[[X] =>> Ctx => X] { - def [A, B](r: Ctx => A) flatMap (f: A => Ctx => B): Ctx => B = +given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] { + def [A, B](r: Ctx => A).flatMap(f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = ctx => x diff --git a/docs/docs/reference/contextual/context-parameters.md b/docs/docs/reference/contextual/using-clauses.md similarity index 100% rename from docs/docs/reference/contextual/context-parameters.md rename to docs/docs/reference/contextual/using-clauses.md diff --git a/docs/docs/reference/other-new-features/control-syntax-new.md b/docs/docs/reference/other-new-features/control-syntax-new.md deleted file mode 100644 index 6e980a982ec9..000000000000 --- a/docs/docs/reference/other-new-features/control-syntax-new.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -layout: doc-page -title: New Control Syntax ---- - -Scala 3 has a new "quiet" syntax for control expressions that does not rely on -enclosing the condition in parentheses, and also allows to drop parentheses or braces -around the generators of a `for`-expression. Examples: -```scala -if x < 0 - "negative" -else if x == 0 - "zero" -else - "positive" - -if x < 0 then -x else x - -while x >= 0 do x = f(x) - -for x <- xs if x > 0 -yield x * x - -for - x <- xs - y <- ys -do - println(x + y) -``` - -The rules in detail are: - - - The condition of an `if`-expression can be written without enclosing parentheses if it is followed by a `then` - or some [indented](./indentation.html) code on a following line. - - The condition of a `while`-loop can be written without enclosing parentheses if it is followed by a `do`. - - The enumerators of a `for`-expression can be written without enclosing parentheses or braces if they are followed by a `yield` or `do`. - - A `do` in a `for`-expression expresses a `for`-loop. - -### Rewrites - -The Dotty compiler can rewrite source code from old syntax and new syntax and back. -When invoked with options `-rewrite -new-syntax` it will rewrite from old to new syntax, dropping parentheses and braces in conditions and enumerators. When invoked with with options `-rewrite -old-syntax` it will rewrite in the reverse direction, inserting parentheses and braces as needed. diff --git a/docs/docs/reference/other-new-features/control-syntax.md b/docs/docs/reference/other-new-features/control-syntax.md index 49c9aa97b5f0..e43c89f8a06f 100644 --- a/docs/docs/reference/other-new-features/control-syntax.md +++ b/docs/docs/reference/other-new-features/control-syntax.md @@ -3,13 +3,17 @@ layout: doc-page title: New Control Syntax --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.20](./control-syntax-new.html). - -Scala 3 has a new "quiet" syntax for control expressions that does not rely in +Scala 3 has a new "quiet" syntax for control expressions that does not rely on enclosing the condition in parentheses, and also allows to drop parentheses or braces around the generators of a `for`-expression. Examples: ```scala +if x < 0 then + "negative" +else if x == 0 + "zero" +else + "positive" + if x < 0 then -x else x while x >= 0 do x = f(x) @@ -26,14 +30,11 @@ do The rules in detail are: - - The condition of an `if`-expression can be written without enclosing parentheses if it is followed by a `then`. + - The condition of an `if`-expression can be written without enclosing parentheses if it is followed by a `then` + or some [indented](./indentation.html) code on a following line. - The condition of a `while`-loop can be written without enclosing parentheses if it is followed by a `do`. - The enumerators of a `for`-expression can be written without enclosing parentheses or braces if they are followed by a `yield` or `do`. - A `do` in a `for`-expression expresses a `for`-loop. - - Newline characters are not statement separators in a condition of an `if` or a `while`. - So the meaning of newlines is the same no matter whether parentheses are present - or absent. - - Newline characters are potential statement separators in the enumerators of a `for`-expression. ### Rewrites diff --git a/docs/docs/reference/other-new-features/implicit-by-name-parameters.md b/docs/docs/reference/other-new-features/implicit-by-name-parameters.md index 51c508193e8d..ff54f8d4a6e3 100644 --- a/docs/docs/reference/other-new-features/implicit-by-name-parameters.md +++ b/docs/docs/reference/other-new-features/implicit-by-name-parameters.md @@ -2,68 +2,4 @@ layout: doc-page title: "Implicit By-Name Parameters" --- - -Call-by-name implicit parameters can be used to avoid a divergent implicit expansion. - -```scala -trait Codec[T] { - def write(x: T): Unit -} - -implicit def intCodec: Codec[Int] = ??? - -implicit def optionCodec[T] - (implicit ev: => Codec[T]): Codec[Option[T]] = - new { - def write(xo: Option[T]) = xo match { - case Some(x) => ev.write(x) - case None => - } - } - -val s = implicitly[Codec[Option[Int]]] - -s.write(Some(33)) -s.write(None) -``` -As is the case for a normal by-name parameter, the argument for the implicit parameter `ev` -is evaluated on demand. In the example above, if the option value `x` is `None`, it is -not evaluated at all. - -The synthesized argument for an implicit parameter is backed by a lazy -val if this is necessary to prevent an otherwise diverging expansion. - -The precise steps for constructing an implicit argument for a by-name parameter of type `=> T` are as follows. - - 1. Create a new implicit value with a fresh name _lv_, which has the signature of the following definition: - - ```scala - implicit lazy val lv: T - ``` - - The current implementation uses the prefix `$lazy_implicit$` followed by a unique integer for _lv_. - - 1. This lazy val is not immediately available as candidate for implicit search (making it immediately available would result in a looping implicit computation). But it becomes available in all nested contexts that look again for an implicit argument to a by-name parameter. - - 1. If this implicit search succeeds with expression `E`, and `E` contains references to the lazy implicit value _lv_, replace `E` by - - - ```scala - { implicit lazy val lv: T = E; lv } - ``` - - Otherwise, return `E` unchanged. - -In the example above, the definition of `s` would be expanded as follows. - -```scala -val s = implicitly[Test.Codec[Option[Int]]]( - optionCodec[Int](intCodec)) -``` - -No lazy val was generated because the synthesized argument is not recursive. - -### Reference - -For more info, see [Issue #1998](https://github.com/lampepfl/dotty/issues/1998) -and the associated [Scala SIP](https://docs.scala-lang.org/sips/byname-implicits.html). +The contents of this page have [moved](../contextual/by-name-context-parameters.html). diff --git a/docs/docs/reference/other-new-features/indentation-new.md b/docs/docs/reference/other-new-features/indentation-new.md deleted file mode 100644 index bbb903a730fd..000000000000 --- a/docs/docs/reference/other-new-features/indentation-new.md +++ /dev/null @@ -1,295 +0,0 @@ ---- -layout: doc-page -title: Optional Braces ---- - -As an experimental feature, Scala 3 enforces some rules on indentation and allows -some occurrences of braces `{...}` to be optional. - - - First, some badly indented programs are ruled out, which means they are flagged with warnings. - - Second, some occurrences of braces `{...}` are made optional. Generally, the rule - is that adding a pair of optional braces will not change the meaning of a well-indented program. - -### Indentation Rules - -The compiler enforces two rules for well-indented programs, flagging violations as warnings. - - 1. In a brace-delimited region, no statement is allowed to start to the left - of the first statement after the opening brace that starts a new line. - - This rule is helpful for finding missing closing braces. It prevents errors like: - - ```scala - if (x < 0) { - println(1) - println(2) - - println("done") // error: indented too far to the left - ``` - - 2. If significant indentation is turned off (i.e. under Scala-2 mode or under `-noindent`) and we are at the start of an indented sub-part of an expression, and the indented part ends in a newline, the next statement must start at an indentation width less than the sub-part. This prevents errors where an opening brace was forgotten, as in - - ```scala - if (x < 0) - println(1) - println(2) // error: missing `{` - ``` - -These rules still leave a lot of leeway how programs should be indented. For instance, they do not impose -any restrictions on indentation within expressions, nor do they require that all statements of an indentation block line up exactly. - -The rules are generally helpful in pinpointing the root cause of errors related to missing opening or closing braces. These errors are often quite hard to diagnose, in particular in large programs. - -### Optional Braces - -The compiler will insert `` or `` -tokens at certain line breaks. Grammatically, pairs of `` and `` tokens have the same effect as pairs of braces `{` and `}`. - -The algorithm makes use of a stack `IW` of previously encountered indentation widths. The stack initially holds a single element with a zero indentation width. The _current indentation width_ is the indentation width of the top of the stack. - -There are two rules: - - 1. An `` is inserted at a line break, if - - - An indentation region can start at the current position in the source, and - - the first token on the next line has an indentation width strictly greater - than the current indentation width - - An indentation region can start - - - after the condition of an `if-else`, or - - after a ": at end of line" token (see below) - - after one of the following tokens: - ``` - = => <- if then else while do try catch finally for yield match return - ``` - If an `` is inserted, the indentation width of the token on the next line - is pushed onto `IW`, which makes it the new current indentation width. - - 2. An `` is inserted at a line break, if - - - the first token on the next line has an indentation width strictly less - than the current indentation width, and - - the first token on the next line is not a - [leading infix operator](../changed-features/operators.html). - - If an `` is inserted, the top element if popped from `IW`. - If the indentation width of the token on the next line is still less than the new current indentation width, step (2) repeats. Therefore, several `` tokens - may be inserted in a row. - - An `` is also inserted if the next statement following a statement sequence starting with an `` closes an indentation region, i.e. is one of `then`, `else`, `do`, `catch`, `finally`, `yield`, `}` or `case`. - -It is an error if the indentation width of the token following an `` does not -match the indentation of some previous line in the enclosing indentation region. For instance, the following would be rejected. -```scala -if x < 0 - -x - else // error: `else` does not align correctly - x -``` -Indentation tokens are only inserted in regions where newline statement separators are also inferred: -at the toplevel, inside braces `{...}`, but not inside parentheses `(...)`, patterns or types. - -### Optional Braces Around Template Bodies - -The Scala grammar uses the term _template body_ for the definitions of a class, trait, object, given instance or extension that are normally enclosed in braces. The braces around a template body can also be omitted by means of the following rule: - -If at the point where a template body can start there is a `:` that occurs at the end -of a line, and that is followed by at least one indented statement, the recognized -token is changed from ":" to ": at end of line". The latter token is one of the tokens -that can start an indentation region. The Scala grammar is changed so an optional ": at end of line" is allowed in front of a template body. - -Analogous rules apply for enum bodies, type refinements, definitions in an instance creation expressions, and local packages containing nested definitions. - -With these new rules, the following constructs are all valid: -```scala -trait A: - def f: Int - -class C(x: Int) extends A: - def f = x - -object O: - def f = 3 - -enum Color: - case Red, Green, Blue - -type T = A: - def f: Int - -given [T] with Ord[T] as Ord[List[T]]: - def compare(x: List[T], y: List[T]) = ??? - -extension on (xs: List[Int]): - def second: Int = xs.tail.head - -new A: - def f = 3 - -package p: - def a = 1 -package q: - def b = 2 -``` - -The syntax changes allowing this are as follows: -``` -TemplateBody ::= [colonEol] ‘{’ [SelfType] TemplateStat {semi TemplateStat} ‘}’ -EnumBody ::= [colonEol] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ -Packaging ::= ‘package’ QualId [colonEol] ‘{’ TopStatSeq ‘}’ -RefinedType ::= AnnotType {[colonEol] Refinement} -``` -Here, `colonEol` stands for ": at end of line", as described above. -The lexical analyzer is modified so that a `:` at the end of a line -is reported as `colonEol` if the parser is at a point where a `colonEol` is -valid as next token. - -### Spaces vs Tabs - -Indentation prefixes can consist of spaces and/or tabs. Indentation widths are the indentation prefixes themselves, ordered by the string prefix relation. So, so for instance "2 tabs, followed by 4 spaces" is strictly less than "2 tabs, followed by 5 spaces", but "2 tabs, followed by 4 spaces" is incomparable to "6 tabs" or to "4 spaces, followed by 2 tabs". It is an error if the indentation width of some line is incomparable with the indentation width of the region that's current at that point. To avoid such errors, it is a good idea not to mix spaces and tabs in the same source file. - -### Indentation and Braces - -Indentation can be mixed freely with braces. For interpreting indentation inside braces, the following rules apply. - - 1. The assumed indentation width of a multiline region enclosed in braces is the - indentation width of the first token that starts a new line after the opening brace. - - 2. On encountering a closing brace `}`, as many `` tokens as necessary are - inserted to close all open indentation regions inside the pair of braces. - -### Special Treatment of Case Clauses - -The indentation rules for `match` expressions and `catch` clauses are refined as follows: - - - An indentation region is opened after a `match` or `catch` also if the following `case` - appears at the indentation width that's current for the `match` itself. - - In that case, the indentation region closes at the first token at that - same indentation width that is not a `case`, or at any token with a smaller - indentation width, whichever comes first. - -The rules allow to write `match` expressions where cases are not indented themselves, as in the example below: -```scala -x match -case 1 => print("I") -case 2 => print("II") -case 3 => print("III") -case 4 => print("IV") -case 5 => print("V") - -println(".") -``` - -### The End Marker - -Indentation-based syntax has many advantages over other conventions. But one possible problem is that it makes it hard to discern when a large indentation region ends, since there is no specific token that delineates the end. Braces are not much better since a brace by itself also contains no information about what region is closed. - -To solve this problem, Scala 3 offers an optional `end` marker. Example -```scala -def largeMethod(...) = - ... - if ... then ... - else - ... // a large block - end if - ... // more code -end largeMethod -``` -An `end` marker consists of the identifier `end` which follows an `` token, and is in turn followed on the same line by exactly one other token, which is either an identifier or one of the reserved words -```scala -if while for match try new -``` -If `end` is followed by a reserved word, the compiler checks that the marker closes an indentation region belonging to a construct that starts with the reserved word. If it is followed by an identifier _id_, the compiler checks that the marker closes a definition -that defines _id_ or a package clause that refers to _id_. - -`end` itself is a soft keyword. It is only treated as an `end` marker if it -occurs at the start of a line and is followed by an identifier or one of the reserved words above. - -It is recommended that `end` markers are used for code where the extent of an indentation region is not immediately apparent "at a glance". Typically this is the case if an indentation region spans 20 lines or more. - -### Example - -Here is a (somewhat meta-circular) example of code using indentation. It provides a concrete representation of indentation widths as defined above together with efficient operations for constructing and comparing indentation widths. - -```scala -enum IndentWidth with - case Run(ch: Char, n: Int) - case Conc(l: IndentWidth, r: Run) - - def <= (that: IndentWidth): Boolean = this match - case Run(ch1, n1) => - that match - case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0) - case Conc(l, r) => this <= l - case Conc(l1, r1) => - that match - case Conc(l2, r2) => l1 == l2 && r1 <= r2 - case _ => false - - def < (that: IndentWidth): Boolean = - this <= that && !(that <= this) - - override def toString: String = this match - case Run(ch, n) => - val kind = ch match - case ' ' => "space" - case '\t' => "tab" - case _ => s"'$ch'-character" - val suffix = if n == 1 then "" else "s" - s"$n $kind$suffix" - case Conc(l, r) => - s"$l, $r" - -object IndentWidth with - private inline val MaxCached = 40 - - private val spaces = IArray.tabulate(MaxCached + 1)(new Run(' ', _)) - private val tabs = IArray.tabulate(MaxCached + 1)(new Run('\t', _)) - - def Run(ch: Char, n: Int): Run = - if n <= MaxCached && ch == ' ' then - spaces(n) - else if n <= MaxCached && ch == '\t' then - tabs(n) - else - new Run(ch, n) - end Run - - val Zero = Run(' ', 0) -end IndentWidth -``` - -### Settings and Rewrites - -Significant indentation is enabled by default. It can be turned off by giving any of the options `-noindent`, `old-syntax` and `language:Scala2`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues a warning. - -The Dotty compiler can rewrite source code to indented code and back. -When invoked with options `-rewrite -indent` it will rewrite braces to -indented regions where possible. When invoked with with options `-rewrite -noindent` it will rewrite in the reverse direction, inserting braces for indentation regions. -The `-indent` option only works on [new-style syntax](./control-syntax.html). So to go from old-style syntax to new-style indented code one has to invoke the compiler twice, first with options `-rewrite -new-syntax`, then again with options -`-rewrite-indent`. To go in the opposite direction, from indented code to old-style syntax, it's `-rewrite -noindent`, followed by `-rewrite -old-syntax`. - -### Variant: Indentation Marker `:` - -Generally, the possible indentation regions coincide with those regions where braces `{...}` are also legal, no matter whether the braces enclose an expression or a set of definitions. There is one exception, though: Arguments to function can be enclosed in braces but they cannot be simply indented instead. Making indentation always significant for function arguments would be too restrictive and fragile. - -To allow such arguments to be written without braces, a variant of the indentation scheme is implemented under -option `-Yindent-colons`. This variant is more contentious and less stable than the rest of the significant indentation scheme. In this variant, a colon `:` at the end of a line is also one of the possible tokens that opens an indentation region. Examples: - -```scala -times(10): - println("ah") - println("ha") -``` -or -```scala -xs.map: - x => - val y = x - 1 - y * y -``` -Colons at the end of lines are their own token, distinct from normal `:`. -The Scala grammar is changed in this variant so that colons at end of lines are accepted at all points -where an opening brace enclosing a function argument is legal. Special provisions are taken so that method result types can still use a colon on the end of a line, followed by the actual type on the next. - diff --git a/docs/docs/reference/other-new-features/indentation.md b/docs/docs/reference/other-new-features/indentation.md index b7c4fdfeb086..e4dc8074d323 100644 --- a/docs/docs/reference/other-new-features/indentation.md +++ b/docs/docs/reference/other-new-features/indentation.md @@ -3,12 +3,45 @@ layout: doc-page title: Optional Braces --- -**Note** The syntax described in this section is currently under revision. -[Here is the new version which will be implemented in Dotty 0.22](./indentation-new.html). - -As an experimental feature, Scala 3 treats indentation as significant and allows +As an experimental feature, Scala 3 enforces some rules on indentation and allows some occurrences of braces `{...}` to be optional. + - First, some badly indented programs are ruled out, which means they are flagged with warnings. + - Second, some occurrences of braces `{...}` are made optional. Generally, the rule + is that adding a pair of optional braces will not change the meaning of a well-indented program. + +### Indentation Rules + +The compiler enforces two rules for well-indented programs, flagging violations as warnings. + + 1. In a brace-delimited region, no statement is allowed to start to the left + of the first statement after the opening brace that starts a new line. + + This rule is helpful for finding missing closing braces. It prevents errors like: + + ```scala + if (x < 0) { + println(1) + println(2) + + println("done") // error: indented too far to the left + ``` + + 2. If significant indentation is turned off (i.e. under Scala-2 mode or under `-noindent`) and we are at the start of an indented sub-part of an expression, and the indented part ends in a newline, the next statement must start at an indentation width less than the sub-part. This prevents errors where an opening brace was forgotten, as in + + ```scala + if (x < 0) + println(1) + println(2) // error: missing `{` + ``` + +These rules still leave a lot of leeway how programs should be indented. For instance, they do not impose +any restrictions on indentation within expressions, nor do they require that all statements of an indentation block line up exactly. + +The rules are generally helpful in pinpointing the root cause of errors related to missing opening or closing braces. These errors are often quite hard to diagnose, in particular in large programs. + +### Optional Braces + The compiler will insert `` or `` tokens at certain line breaks. Grammatically, pairs of `` and `` tokens have the same effect as pairs of braces `{` and `}`. @@ -24,11 +57,11 @@ There are two rules: An indentation region can start - - at points where a set of definitions enclosed in braces is expected in a - class, object, given, or enum definition, in an enum case, or after a package clause, or + - after the condition of an `if-else`, or + - after a ": at end of line" token (see below) - after one of the following tokens: ``` - = => <- if then else while do try catch finally for yield match + = => <- if then else while do try catch finally for yield match return ``` If an `` is inserted, the indentation width of the token on the next line is pushed onto `IW`, which makes it the new current indentation width. @@ -44,10 +77,12 @@ There are two rules: If the indentation width of the token on the next line is still less than the new current indentation width, step (2) repeats. Therefore, several `` tokens may be inserted in a row. + An `` is also inserted if the next statement following a statement sequence starting with an `` closes an indentation region, i.e. is one of `then`, `else`, `do`, `catch`, `finally`, `yield`, `}` or `case`. + It is an error if the indentation width of the token following an `` does not match the indentation of some previous line in the enclosing indentation region. For instance, the following would be rejected. ```scala -if x < 0 then +if x < 0 -x else // error: `else` does not align correctly x @@ -55,13 +90,68 @@ if x < 0 then Indentation tokens are only inserted in regions where newline statement separators are also inferred: at the toplevel, inside braces `{...}`, but not inside parentheses `(...)`, patterns or types. +### Optional Braces Around Template Bodies + +The Scala grammar uses the term _template body_ for the definitions of a class, trait, object, given instance or extension that are normally enclosed in braces. The braces around a template body can also be omitted by means of the following rule: + +If at the point where a template body can start there is a `:` that occurs at the end +of a line, and that is followed by at least one indented statement, the recognized +token is changed from ":" to ": at end of line". The latter token is one of the tokens +that can start an indentation region. The Scala grammar is changed so an optional ": at end of line" is allowed in front of a template body. + +Analogous rules apply for enum bodies, type refinements, definitions in an instance creation expressions, and local packages containing nested definitions. + +With these new rules, the following constructs are all valid: +```scala +trait A: + def f: Int + +class C(x: Int) extends A: + def f = x + +object O: + def f = 3 + +enum Color: + case Red, Green, Blue + +type T = A: + def f: Int + +given [T] with Ord[T] as Ord[List[T]]: + def compare(x: List[T], y: List[T]) = ??? + +extension on (xs: List[Int]): + def second: Int = xs.tail.head + +new A: + def f = 3 + +package p: + def a = 1 +package q: + def b = 2 +``` + +The syntax changes allowing this are as follows: +``` +TemplateBody ::= [colonEol] ‘{’ [SelfType] TemplateStat {semi TemplateStat} ‘}’ +EnumBody ::= [colonEol] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ +Packaging ::= ‘package’ QualId [colonEol] ‘{’ TopStatSeq ‘}’ +RefinedType ::= AnnotType {[colonEol] Refinement} +``` +Here, `colonEol` stands for ": at end of line", as described above. +The lexical analyzer is modified so that a `:` at the end of a line +is reported as `colonEol` if the parser is at a point where a `colonEol` is +valid as next token. + ### Spaces vs Tabs Indentation prefixes can consist of spaces and/or tabs. Indentation widths are the indentation prefixes themselves, ordered by the string prefix relation. So, so for instance "2 tabs, followed by 4 spaces" is strictly less than "2 tabs, followed by 5 spaces", but "2 tabs, followed by 4 spaces" is incomparable to "6 tabs" or to "4 spaces, followed by 2 tabs". It is an error if the indentation width of some line is incomparable with the indentation width of the region that's current at that point. To avoid such errors, it is a good idea not to mix spaces and tabs in the same source file. ### Indentation and Braces -Indentatation can be mixed freely with braces. For interpreting indentation inside braces, the following rules apply. +Indentation can be mixed freely with braces. For interpreting indentation inside braces, the following rules apply. 1. The assumed indentation width of a multiline region enclosed in braces is the indentation width of the first token that starts a new line after the opening brace. @@ -69,7 +159,6 @@ Indentatation can be mixed freely with braces. For interpreting indentation ins 2. On encountering a closing brace `}`, as many `` tokens as necessary are inserted to close all open indentation regions inside the pair of braces. - ### Special Treatment of Case Clauses The indentation rules for `match` expressions and `catch` clauses are refined as follows: @@ -109,7 +198,7 @@ end largeMethod ``` An `end` marker consists of the identifier `end` which follows an `` token, and is in turn followed on the same line by exactly one other token, which is either an identifier or one of the reserved words ```scala -if while for match try new given +if while for match try new given extension ``` If `end` is followed by a reserved word, the compiler checks that the marker closes an indentation region belonging to a construct that starts with the reserved word. If it is followed by an identifier _id_, the compiler checks that the marker closes a definition that defines _id_ or a package clause that refers to _id_. @@ -124,37 +213,35 @@ It is recommended that `end` markers are used for code where the extent of an in Here is a (somewhat meta-circular) example of code using indentation. It provides a concrete representation of indentation widths as defined above together with efficient operations for constructing and comparing indentation widths. ```scala -enum IndentWidth +enum IndentWidth: case Run(ch: Char, n: Int) case Conc(l: IndentWidth, r: Run) - def <= (that: IndentWidth): Boolean = - this match - case Run(ch1, n1) => - that match - case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0) - case Conc(l, r) => this <= l - case Conc(l1, r1) => - that match - case Conc(l2, r2) => l1 == l2 && r1 <= r2 - case _ => false + def <= (that: IndentWidth): Boolean = this match + case Run(ch1, n1) => + that match + case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0) + case Conc(l, r) => this <= l + case Conc(l1, r1) => + that match + case Conc(l2, r2) => l1 == l2 && r1 <= r2 + case _ => false def < (that: IndentWidth): Boolean = this <= that && !(that <= this) - override def toString: String = - this match - case Run(ch, n) => - val kind = ch match - case ' ' => "space" - case '\t' => "tab" - case _ => s"'$ch'-character" - val suffix = if n == 1 then "" else "s" - s"$n $kind$suffix" - case Conc(l, r) => - s"$l, $r" - -object IndentWidth + override def toString: String = this match + case Run(ch, n) => + val kind = ch match + case ' ' => "space" + case '\t' => "tab" + case _ => s"'$ch'-character" + val suffix = if n == 1 then "" else "s" + s"$n $kind$suffix" + case Conc(l, r) => + s"$l, $r" + +object IndentWidth: private inline val MaxCached = 40 private val spaces = IArray.tabulate(MaxCached + 1)(new Run(' ', _)) @@ -175,7 +262,7 @@ end IndentWidth ### Settings and Rewrites -Significant indentation is enabled by default. It can be turned off by giving any of the options `-noindent`, `old-syntax` and `language:Scala2`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues an error (or, in the case of `-language:Scala2Compat`, a migration warning). +Significant indentation is enabled by default. It can be turned off by giving any of the options `-noindent`, `old-syntax` and `language:Scala2`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues a warning. The Dotty compiler can rewrite source code to indented code and back. When invoked with options `-rewrite -indent` it will rewrite braces to diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 00bf068d7d11..4b976d2957d6 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -44,13 +44,13 @@ sidebar: - title: Overview url: docs/reference/contextual/motivation.html - title: Given Instances - url: docs/reference/contextual/delegates.html - - title: Given Clauses - url: docs/reference/contextual/given-clauses.html + url: docs/reference/contextual/givens.html + - title: Using Clauses + url: docs/reference/contextual/using-clauses.html - title: Context Bounds url: docs/reference/contextual/context-bounds.html - title: Given Imports - url: docs/reference/contextual/import-delegate.html + url: docs/reference/contextual/given-imports.html - title: Extension Methods url: docs/reference/contextual/extension-methods.html - title: Implementing Typeclasses @@ -59,12 +59,12 @@ sidebar: url: docs/reference/contextual/derivation.html - title: Multiversal Equality url: docs/reference/contextual/multiversal-equality.html - - title: Implicit Function Types - url: docs/reference/contextual/implicit-function-types.html + - title: Context Functions + url: docs/reference/contextual/context-functions.html - title: Implicit Conversions url: docs/reference/contextual/conversions.html - - title: Implicit By-Name Parameters - url: docs/reference/contextual/implicit-by-name-parameters.html + - title: By-Name Context Parameters + url: docs/reference/contextual/by-name-context-parameters.html - title: Relationship with Scala 2 Implicits url: docs/reference/contextual/relationship-implicits.html - title: Metaprogramming