From 6fac698af6af4fe7e7c8f5b121074c45992c466a Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Mon, 13 Jul 2020 21:05:26 -0700 Subject: [PATCH 1/5] Adjust the title levels in context-bounds.md --- docs/docs/reference/contextual/context-bounds.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/docs/reference/contextual/context-bounds.md b/docs/docs/reference/contextual/context-bounds.md index 2ae0e932c7b8..0b85f1a3a0fc 100644 --- a/docs/docs/reference/contextual/context-bounds.md +++ b/docs/docs/reference/contextual/context-bounds.md @@ -3,26 +3,31 @@ 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 +## 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. @@ -32,7 +37,7 @@ context parameter stemming from a context bound with a normal argument will give warning. The warning indicates that a `(using ...)` clause is needed instead. The rewrite can be done automatically under `-rewrite`. -### Syntax +## Syntax ``` TypeParamBounds ::= [SubtypeBounds] {ContextBound} From 4cf89cc6e8c3f99198ec095cb5e22067e6b1ee1e Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Mon, 13 Jul 2020 21:24:19 -0700 Subject: [PATCH 2/5] Adjust and correct the markdown format in extension-methods.md and correct a code example --- .../reference/contextual/extension-methods.md | 61 ++++++++++++------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/docs/docs/reference/contextual/extension-methods.md b/docs/docs/reference/contextual/extension-methods.md index dc44d708ed50..d6aea77b3ef8 100644 --- a/docs/docs/reference/contextual/extension-methods.md +++ b/docs/docs/reference/contextual/extension-methods.md @@ -30,9 +30,11 @@ def extension_circumference(c: Circle): Double = c.radius * math.Pi * 2 assert(circle.circumference == extension_circumference(circle)) ``` + ### Operators The extension method syntax can also be used to define operators. Examples: + ```scala extension (x: String) def < (y: String): Boolean = ... @@ -47,11 +49,13 @@ x min 3 ``` The three definitions above translate to + ```scala def extension_< (x: String)(y: String): Boolean = ... def extension_+: (xs: Seq[Elem])(x: Elem): Seq[Elem] = ... @infix def extension_min(x: Number)(y: Number): Number = ... ``` + Note the swap of the two parameters `x` and `xs` when translating the right-associative operator `+:` to an extension method. This is analogous to the implementation of right binding operators as normal methods. The Scala @@ -73,13 +77,15 @@ the two swaps cancel each other out). If an extension method has type parameters, they come immediately after `extension` and are followed by the extended parameter. When calling a generic extension method, any explicitly given type arguments follow the method name. So the `second` method could be instantiated as follows. + ```scala List(1, 2, 3).second[Int] ``` -Of course, the type argument here would usually be left out since it can be inferred. +Of course, the type argument here would usually be left out since it can be inferred. Extensions can also take using clauses. For instance, the `+` extension above could equivalently be written with a using clause: + ```scala extension [T](x: T)(using n: Numeric[T]) def - (y: T): T = n.minus(x, y) @@ -94,6 +100,7 @@ Sometimes, one wants to define several extension methods that share the same left-hand parameter type. In this case one can "pull out" the common parameters into a single extension and enclose all methods in braces or an indented region following a '`:`'. Example: + ```scala extension (ss: Seq[String]) @@ -109,6 +116,7 @@ assuming the common extended value `ss` as receiver. Collective extensions like these are a shorthand for individual extensions where each method is defined separately. For instance, the first extension above expands to + ```scala extension (ss: Seq[String]) def longestStrings: Seq[String] = @@ -118,7 +126,9 @@ extension (ss: Seq[String]) extension (ss: Seq[String]) def longestString: String = ss.longestStrings.head ``` + Collective extensions also can take type parameters and have using clauses. Example + ```scala extension [T](xs: List[T])(using Ordering[T]) def smallest(n: Int): List[T] = xs.sorted.take(n) @@ -167,7 +177,9 @@ trait SafeDiv: case (Some(d), Some(r)) => Some((d, r)) case _ => None ``` + By the second rule, an extension method can be made available by defining a given instance containing it, like this: + ```scala given ops1 as IntOps // brings safeMod into scope @@ -175,6 +187,7 @@ given ops1 as IntOps // brings safeMod into scope ``` By the third and fourth rule, an extension method is available if it is in the implicit scope of the receiver type or in a given instance in that scope. Example: + ```scala class List[T]: ... @@ -204,9 +217,9 @@ Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type ar 2. If the first rewriting does not typecheck with expected type `T`, and there is an extension method `m` in some eligible object `o`, the selection is rewritten to `o.extension_m[Ts](e)`. An object `o` is _eligible_ if - - `o` forms part of the implicit scope of `T`, or - - `o` is a given instance that is visible at the point of the application, or - - `o` is a given instance in the implicit scope of `T`. + - `o` forms part of the implicit scope of `T`, or + - `o` is a given instance that is visible at the point of the application, or + - `o` is a given instance in the implicit scope of `T`. This second rewriting is attempted at the time where the compiler also tries an implicit conversion from `T` to a type containing `m`. If there is more than one way of rewriting, an ambiguity error results. @@ -214,43 +227,48 @@ Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type ar An extension method can also be used as an identifier by itself. If an identifier `m` does not resolve, the identifier is rewritten to: - - `x.m` if the identifier appears in an extension with parameter `x` - - `this.m` otherwise +- `x.m` if the identifier appears in an extension with parameter `x` +- `this.m` otherwise and the rewritten term is again tried as an application of an extension method. Example: + ```scala - extension (s: String) - def position(ch: Char, n: Int): Int = - if n < s.length && s(n) != ch then position(ch, n + 1) - else n +extension (s: String) + def position(ch: Char, n: Int): Int = + if n < s.length && s(n) != ch then position(ch, n + 1) + else n ``` + The recursive call `position(ch, n + 1)` expands to `s.position(ch, n + 1)` in this case. The whole extension method rewrites to + ```scala def extension_position(s: String)(ch: Char, n: Int): Int = if n < s.length && s(n) != ch then extension_position(s)(ch, n + 1) else n ``` + ### More Details 1. To avoid confusion, names of normal methods are not allowed to start with `extension_`. -2. A named import such as `import a.m` of an extension method in `a` will make `m` -only available as an extension method. To access it under -`extension_m` that name as to be imported separately. Example: -```scala -object DoubleOps: - extension (x: Double) def ** (exponent: Int): Double = - require(exponent > 0) - if exponent == 0 then 1 else x * (x ** (exponent - 1)) +2. A named import such as `import a.m` of an extension method in `a` will make `m` only available as an extension method. + To access it under `extension_m` that name as to be imported separately. Example: -import DoubleOps.{**, extension_**} -assert(2.0 ** 3 == extension_**(2.0)(3)) -``` + ```scala + object DoubleOps: + extension (x: Double) def ** (exponent: Int): Double = + require(exponent >= 0) + if exponent == 0 then 1 else x * (x ** (exponent - 1)) + + import DoubleOps.{**, extension_**} + assert(2.0 ** 3 == extension_**(2.0)(3)) + ``` ### Syntax Here are the syntax changes for extension methods and collective extensions relative to the [current syntax](../../internals/syntax.md). + ``` BlockStat ::= ... | Extension TemplateStat ::= ... | Extension @@ -260,6 +278,7 @@ Extension ::= ‘extension’ [DefTypeParamClause] ‘(’ DefParam ‘ ExtMethods ::= ExtMethod | [nl] ‘{’ ExtMethod {semi ExtMethod ‘}’ ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef ``` + `extension` is a soft keyword. It is recognized as a keyword only if it appears at the start of a statement and is followed by `[` or `(`. In all other cases it is treated as an identifier. From 9e3da244bfbf351152e89c92c1671d72e5b4c132 Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Mon, 13 Jul 2020 21:35:31 -0700 Subject: [PATCH 3/5] Fix a typo and remove trailing : in the titles of type-classes.md --- .../docs/reference/contextual/type-classes.md | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/docs/reference/contextual/type-classes.md b/docs/docs/reference/contextual/type-classes.md index e8489d393fe5..dfd5c32b9833 100644 --- a/docs/docs/reference/contextual/type-classes.md +++ b/docs/docs/reference/contextual/type-classes.md @@ -4,13 +4,14 @@ title: "Implementing Type classes" --- A _type class_ is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. This can be useful in multiple use-cases, for example: + * expressing how a type you don't own (from the standard or 3rd-party library) conforms to such behavior * expressing such a behavior for multiple types without involving sub-typing relationships (one `extends` another) between those types (see: [ad hoc polymorphism](https://en.wikipedia.org/wiki/Ad_hoc_polymorphism) for instance) Therefore in Scala 3, _type classes_ are just _traits_ with one or more parameters whose implementations are not defined through the `extends` keyword, but by **given instances**. Here are some examples of common type classes: -### Semigroups and monoids: +### Semigroups and monoids Here's the `Monoid` type class definition: @@ -31,6 +32,7 @@ given Monoid[String]: ``` Whereas for the type `Int` one could write the following: + ```scala given Monoid[Int]: extension (x: Int) def combine (y: Int): Int = x + y @@ -58,7 +60,7 @@ def combineAll[T: Monoid](xs: List[T]): T = xs.foldLeft(Monoid[T].unit)(_.combine(_)) ``` -### Functors: +### Functors A `Functor` for a type provides the ability for its values to be "mapped over", i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements. We can represent all types that can be "mapped over" with `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. @@ -69,6 +71,7 @@ The definition of a generic `Functor` would thus be written as: trait Functor[F[_]]: def map[A, B](x: F[A], f: A => B): F[B] ``` + Which could read as follows: "A `Functor` for the type constructor `F[_]` represents the ability to transform `F[A]` to `F[B]` through the application of function `f` with type `A => B`". We call the `Functor` definition here a _type class_. This way, we could define an instance of `Functor` for the `List` type: @@ -81,6 +84,7 @@ given Functor[List]: With this `given` instance in scope, everywhere a `Functor` is expected, the compiler will accept a `List` to be used. For instance, we may write such a testing method: + ```scala def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = assert(expected == summon[Functor[F]].map(original, mapping)) @@ -117,18 +121,18 @@ It simplifies the `assertTransformation` method: def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = assert(expected == original.map(mapping)) ``` + The `map` method is now directly used on `original`. It is available as an extension method since `original`'s type is `F[A]` and a given instance for `Functor[F[A]]` which defines `map` is in scope. - ### Monads Applying `map` in `Functor[List]` to a mapping function of type `A => B` results in a `List[B]`. So applying it to a mapping function of type `A => List[B]` results in a `List[List[B]]`. To avoid managing lists of lists, we may want to "flatten" the values in a single list. That's where `Monad` comes in. A `Monad` for type `F[_]` is a `Functor[F]` with two more operations: -* `flatMap`, which turns an `F[A]` into an `F[B]` when given a function of type -`A => F[B]`, + +* `flatMap`, which turns an `F[A]` into an `F[B]` when given a function of type `A => F[B]`, * `pure`, which creates an `F[A]` from a single value `A`. Here is the translation of this definition in Scala 3: @@ -152,6 +156,7 @@ end Monad #### List A `List` can be turned into a monad via this `given` instance: + ```scala given listMonad as Monad[List]: def pure[A](x: A): List[A] = @@ -160,6 +165,7 @@ given listMonad as Monad[List]: def flatMap(f: A => List[B]): List[B] = xs.flatMap(f) // rely on the existing `flatMap` method of `List` ``` + Since `Monad` is a subtype of `Functor`, `List` is also a functor. The Functor's `map` operation is already provided by the `Monad` trait, so the instance does not need to define it explicitly. @@ -196,13 +202,17 @@ def layout(str: String)(config: Config): Unit = ??? We may want to combine `compute` and `show` into a single function, accepting a `Config` as parameter, and showing the result of the computation, and we'd like to use a monad to avoid passing the parameter explicitly multiple times. So postulating the right `flatMap` operation, we could write: + ```scala def computeAndShow(i: Int): Config => Unit = compute(i).flatMap(show) ``` + instead of + ```scala show(compute(i)(config))(config) ``` + Let's define this m then. First, we are going to define a type named `ConfigDependent` representing a function that when passed a `Config` produces a `Result`. ```scala @@ -232,7 +242,6 @@ type ConfigDependent = [Result] =>> Config => Result Using this syntax would turn the previous `configDependentMonad` into: - ```scala given configDependentMonad as Monad[[Result] =>> Config => Result] @@ -266,7 +275,7 @@ end readerMonad The definition of a _type class_ is expressed with a parameterised type with abstract members, such as a `trait`. The main difference between subtype polymorphism and ad-hoc polymorphism with _type classes_ is how the definition of the _type class_ is implemented, in relation to the type it acts upon. In the case of a _type class_, its implementation for a concrete type is expressed through a `given` instance definition, which is supplied as an implicit argument alongside the value it acts upon. With subtype polymorphism, the implementation is mixed into the parents of a class, and only a single term is required to perform a polymorphic operation. The type class solution -takes more effort to set up, but is more extensible: Adding a new interface to a class +takes more effort to set up, but is more extensible: Adding a new interface to a class requires changing the source code of that class. But contrast, instances for type classes can be defined anywhere. To conclude, we have seen that traits and given instances, combined with other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _type classes_. From bc8a132b990d69fc4db0b0690f0315665e03161f Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Mon, 13 Jul 2020 21:44:47 -0700 Subject: [PATCH 4/5] Fix a code example that was not consistent with its description --- .../reference/contextual/extension-methods.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/docs/reference/contextual/extension-methods.md b/docs/docs/reference/contextual/extension-methods.md index d6aea77b3ef8..590eee5481fa 100644 --- a/docs/docs/reference/contextual/extension-methods.md +++ b/docs/docs/reference/contextual/extension-methods.md @@ -65,21 +65,21 @@ the two swaps cancel each other out). ### Generic Extensions - It is also possible to extend generic types by adding type parameters to an extension. For instance: +It is also possible to extend generic types by adding type parameters to an extension. For instance: - ```scala - extension [T](xs: List[T]) - def second = xs.tail.head +```scala +extension [T](xs: List[T]) + def second = xs.tail.head - extension [T: Numeric](x: T) - def + (y: T): T = summon[Numeric[T]].plus(x, y) +extension [T: Numeric](x: T) + def + (y: T): T = summon[Numeric[T]].plus(x, y) ``` If an extension method has type parameters, they come immediately after `extension` and are followed by the extended parameter. When calling a generic extension method, any explicitly given type arguments follow the method name. So the `second` method could be instantiated as follows. ```scala - List(1, 2, 3).second[Int] +List(1, 2, 3).second[Int] ``` Of course, the type argument here would usually be left out since it can be inferred. @@ -87,8 +87,8 @@ Of course, the type argument here would usually be left out since it can be infe Extensions can also take using clauses. For instance, the `+` extension above could equivalently be written with a using clause: ```scala - extension [T](x: T)(using n: Numeric[T]) - def - (y: T): T = n.minus(x, y) +extension [T](x: T)(using n: Numeric[T]) + def + (y: T): T = n.plus(x, y) ``` **Note**: Type parameters have to be given after the `extension` keyword; From 849484dbcefeab00f789ae7ea5fe8b3b6f67d9f0 Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Mon, 13 Jul 2020 21:56:57 -0700 Subject: [PATCH 5/5] Update the relationship-implicits.md to use up to date concept names --- .../contextual/relationship-implicits.md | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/docs/docs/reference/contextual/relationship-implicits.md b/docs/docs/reference/contextual/relationship-implicits.md index 7b22a84ad8ef..b0a7295e74e5 100644 --- a/docs/docs/reference/contextual/relationship-implicits.md +++ b/docs/docs/reference/contextual/relationship-implicits.md @@ -12,34 +12,45 @@ Many, but not all, of the new contextual abstraction features in Scala 3 can be 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 @@ -48,29 +59,35 @@ final implicit def given_Context = ctx ### Anonymous Given Instances 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 as Ord[Int] { ... } given given_Ord_List_T[T](using ord: Ord[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. +1. the prefix `given_`, +2. the simple name(s) of the implemented type(s), leaving out any prefixes, +3. 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`. -### Given Clauses +### Using Clauses + +Using clauses correspond 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)(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)`. @@ -88,29 +105,33 @@ Context bounds are the same in both language versions. They expand to the respec **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. +will map to using 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 extension (c: Circle) def 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. ### Type class Derivation Type class 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 +### Context Function Types -Implicit function types have no analogue in Scala 2. +Context function types have no analogue in Scala 2. ### Implicit By-Name Parameters @@ -121,16 +142,21 @@ Implicit by-name parameters are not supported in Scala 2, but can be emulated to ### 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(_) ``` @@ -143,10 +169,13 @@ Implicit classes in Scala 2 are often used to define extension methods, which ar 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 @@ -155,10 +184,13 @@ 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