From 38e1086e27920c221543f36973ff64ea2350b9d3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Sep 2019 18:56:20 +0200 Subject: [PATCH 1/3] Allow normal parameters after given parameters With the new given syntax, we can lift the restriction that normal parameters cannot follow given parameters. This restriction was introduced because with the previous syntax this looked too awkward. The added test `given-eta` shows that eta expansion works as expected for these cases. --- .../dotty/tools/dotc/parsing/Parsers.scala | 15 +- .../reference/contextual-new/given-clauses.md | 116 ------------ .../contextual-new/relationship-implicits.md | 176 ------------------ .../reference/contextual/given-clauses.md | 2 +- .../contextual/relationship-implicits.md | 60 +++--- tests/run/given-eta.scala | 19 ++ 6 files changed, 53 insertions(+), 335 deletions(-) delete mode 100644 docs/docs/reference/contextual-new/given-clauses.md delete mode 100644 docs/docs/reference/contextual-new/relationship-implicits.md create mode 100644 tests/run/given-eta.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 384659030c51..13311eebece0 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2849,7 +2849,7 @@ object Parsers { ofCaseClass: Boolean = false, ofInstance: Boolean = false): List[List[ValDef]] = - def recur(firstClause: Boolean, nparams: Int, contextualOnly: Boolean): List[List[ValDef]] = + def recur(firstClause: Boolean, nparams: Int): List[List[ValDef]] = newLineOptWhenFollowedBy(LPAREN) if in.token == LPAREN then val paramsStart = in.offset @@ -2858,23 +2858,14 @@ object Parsers { ofClass = ofClass, ofCaseClass = ofCaseClass, firstClause = firstClause) - val isGiven = params match - case param :: _ if param.mods.flags.is(Given) => true - case _ => - if contextualOnly then - syntaxError( - if ofInstance then em"parameters of instance definitions must be `given' clauses" - else em"normal parameters cannot come after `given' clauses", - paramsStart) - false val lastClause = params.nonEmpty && params.head.mods.flags.is(Implicit) params :: ( if lastClause then Nil - else recur(firstClause = false, nparams + params.length, isGiven)) + else recur(firstClause = false, nparams + params.length)) else Nil end recur - recur(firstClause = true, 0, ofInstance) + recur(firstClause = true, 0) end paramClauses /* -------- DEFS ------------------------------------------- */ diff --git a/docs/docs/reference/contextual-new/given-clauses.md b/docs/docs/reference/contextual-new/given-clauses.md deleted file mode 100644 index 2ae9b2e65334..000000000000 --- a/docs/docs/reference/contextual-new/given-clauses.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -layout: doc-page -title: "Given Parameters" ---- - -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 allowed. - -## 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 a 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-new/relationship-implicits.md b/docs/docs/reference/contextual-new/relationship-implicits.md deleted file mode 100644 index c6d18751524d..000000000000 --- a/docs/docs/reference/contextual-new/relationship-implicits.md +++ /dev/null @@ -1,176 +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 Contextual Abstraction with 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: 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., - ```scala - given listOrd[T](given (ord: Ord[T]): 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, - 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() - - 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 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 : Ord[Int] { ... } -given given_Ord_List[T] : 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. - -Anonymous given instances that define extension methods without also implementing a type -get their name from the name of the first extension method and the toplevel type -constructor of its first parameter. For example, the given instance -```scala -given { - def (xs: List[T]) second[T] = ... -} -``` -gets the synthesized name `given_second_of_List_T`. - -### Given Clauses - -Given clauses corresponds largely to Scala-2's implicit parameter clauses. E.g. -```scala -def max[T](x: T, y: T)(given 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`). -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 `the` method corresponds to `implicitly` in Scala 2. -It is precisely the same as the `the` method in Shapeless. -The difference between `the` (in both versions) and `implicitly` is -that `the` 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 with `given` or -with a normal application. Once old-style implicits are deprecated, context bounds -will map to given 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 CircleDeco(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. - -### 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 Dotty - -### 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: Conversion[String, Token] { - def apply(str: String): Token = new KeyWord(str) -} -``` - -### 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 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 symDeco: SymDeco -``` -can be expressed in Dotty as -```scala -def symDeco: SymDeco -given SymDeco = symDeco -``` - -## 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/given-clauses.md b/docs/docs/reference/contextual/given-clauses.md index a3735168ac04..a72746872fd1 100644 --- a/docs/docs/reference/contextual/given-clauses.md +++ b/docs/docs/reference/contextual/given-clauses.md @@ -69,7 +69,7 @@ maximum(xs)(given descending(given ListOrd(given IntOrd))) 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 c: u.Context)(given s: ctx.Symbol, k: ctx.Kind) = ... +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 diff --git a/docs/docs/reference/contextual/relationship-implicits.md b/docs/docs/reference/contextual/relationship-implicits.md index 51825dd25798..c6d18751524d 100644 --- a/docs/docs/reference/contextual/relationship-implicits.md +++ b/docs/docs/reference/contextual/relationship-implicits.md @@ -13,11 +13,11 @@ 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: Ord[Int] { ... } ``` maps to ```scala - implicit object IntOrd extends Ord[Int] { ... } + implicit object IntOrd extends Ord[Int] { ... } ``` 2. Parameterized given instances are mapped to combinations of classes and implicit methods. E.g., ```scala @@ -25,8 +25,8 @@ Given instances can be mapped to combinations of implicit objects, classes and i ``` 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] + 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, 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 @@ -35,23 +35,23 @@ Given instances can be mapped to combinations of implicit objects, classes and i Examples: ```scala - given global: ExecutionContext = new ForkJoinContext() + given global: ExecutionContext = new ForkJoinContext() - val ctx: Context - given Context = ctx + val ctx: Context + given Context = ctx ``` would map to ```scala - final implicit lazy val global: ExecutionContext = new ForkJoinContext() - final implicit def Context_given = ctx + final implicit lazy val global: ExecutionContext = new ForkJoinContext() + 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 : Ord[Int] { ... } - given given_Ord_List[T] : Ord[List[T]] { ... } +given given_Ord_Int : Ord[Int] { ... } +given given_Ord_List[T] : Ord[List[T]] { ... } ``` The synthesized type names are formed from @@ -63,9 +63,9 @@ Anonymous given instances that define extension methods without also implementin get their name from the name of the first extension method and the toplevel type constructor of its first parameter. For example, the given instance ```scala - given { - def (xs: List[T]) second[T] = ... - } +given { + def (xs: List[T]) second[T] = ... +} ``` gets the synthesized name `given_second_of_List_T`. @@ -73,11 +73,11 @@ gets the synthesized name `given_second_of_List_T`. Given clauses corresponds 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)(given ord: Ord[T]): T ``` would be written ```scala - def max[T](x: T, y: T)(implicit ord: Ord[T]): T +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`, @@ -102,13 +102,13 @@ will map to given clauses instead. 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 +def (c: Circle) circumference: Double = c.radius * math.Pi * 2 ``` could be simulated to some degree by ```scala - implicit class CircleDeco(c: Circle) extends AnyVal { - def circumference: Double = c.radius * math.Pi * 2 - } +implicit class CircleDeco(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. @@ -130,13 +130,13 @@ Implicit by-name parameters are not supported in Scala 2, but can be emulated to 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) +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: Conversion[String, Token] { + def apply(str: String): Token = new KeyWord(str) +} ``` ### Implicit Classes @@ -148,24 +148,24 @@ 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 +lazy implicit val pos: Position = tree.sourcePos ``` can be expressed in Dotty as ```scala - lazy val pos: Position = tree.sourcePos - given Position = pos +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 symDeco: SymDeco +implicit def symDeco: SymDeco ``` can be expressed in Dotty as ```scala - def symDeco: SymDeco - given SymDeco = symDeco +def symDeco: SymDeco +given SymDeco = symDeco ``` ## Implementation Status and Timeline diff --git a/tests/run/given-eta.scala b/tests/run/given-eta.scala new file mode 100644 index 000000000000..29d9893c477f --- /dev/null +++ b/tests/run/given-eta.scala @@ -0,0 +1,19 @@ +class C(val x: Int) + +trait D + type T + def trans(other: T): T + +def f(x: Int)(given c: C)(y: Int) = x + c.x + y +def g(x: Int)(given d: D)(y: d.T): d.T = d.trans(y) + +@main def Test = + given C(1) + val x = f + assert(x(2)(3) == 6) + + given D + type T = Int + def trans(other: T) = 2 * other + val y = g + assert(y(2)(3) == 6) From ea37a41871fda2ad4b27aad69b517a9db20d18cc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Sep 2019 19:25:47 +0200 Subject: [PATCH 2/3] Add neg test --- tests/neg/given-eta.scala | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/neg/given-eta.scala diff --git a/tests/neg/given-eta.scala b/tests/neg/given-eta.scala new file mode 100644 index 000000000000..29427a8c58f3 --- /dev/null +++ b/tests/neg/given-eta.scala @@ -0,0 +1,9 @@ + +trait D + type T + def trans(other: T): T + +def h(d: D)(given x: d.T)(y: d.T) = (d.trans(x), d.trans(y)) + +val z = h // error: no implicit argument of type d.T was found for parameter x of method h + From a80e98086159af31a9ac9751f3b284392d9b4a1f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Sep 2019 20:49:13 +0200 Subject: [PATCH 3/3] Fix neg test --- tests/neg/implicit-params.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/neg/implicit-params.scala b/tests/neg/implicit-params.scala index 764e85a8b25b..9e6198c6f297 100644 --- a/tests/neg/implicit-params.scala +++ b/tests/neg/implicit-params.scala @@ -5,11 +5,11 @@ object Test { def f(x: Int)(given c: C) = x + c.x - def g0(x: Int)(given c: C) (y: Int) = x + c.x + y // error + def g0(x: Int)(given c: C) (y: Int) = x + c.x + y // now OK def g(x: Int)(given c: C)(given D) = x + c.x + summon[D].x // OK - def h(x: Int) given () = x // error + def h(x: Int) given () = x // error: missing return type given C : C(11) given D : D(11)