From e17ded16a3006c6d1cc8d7670ef236951ffa2c40 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 Jan 2019 10:33:30 +0100 Subject: [PATCH 01/16] Use `given` for implicit parameters and arguments --- .../src/dotty/tools/dotc/core/Types.scala | 8 +- .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- .../src/dotty/tools/dotc/parsing/Tokens.scala | 3 +- .../src/dotty/tools/dotc/typer/Namer.scala | 4 +- docs/docs/internals/syntax.md | 15 +- .../reference/instances/context-params.md | 51 +++---- .../instances/discussion/motivation.md | 23 +-- .../instances/implicit-conversions.md | 78 ++++++++++ .../instances/implicit-function-types.md | 144 +++++++++--------- .../docs/reference/instances/instance-defs.md | 32 ++-- .../instances/replacing-implicits.md | 66 ++++---- docs/sidebar.yml | 4 +- tests/neg/contextual-params.scala | 16 +- tests/neg/i2514.scala | 2 +- tests/neg/i2514a.scala | 2 +- tests/neg/i2960.scala | 2 +- tests/pos/case-getters.scala | 4 +- tests/pos/depfuntype.scala | 4 +- tests/pos/eff-compose.scala | 2 +- tests/pos/{assumeIn.scala => givenIn.scala} | 6 +- tests/pos/i2146.scala | 8 +- tests/pos/i3692.scala | 4 +- tests/pos/i4725.scala | 8 +- tests/pos/inline-apply.scala | 2 +- tests/pos/reference/instances.scala | 24 +-- tests/run/config.scala | 10 +- tests/run/erased-23.scala | 2 +- tests/run/i2939.scala | 2 +- tests/run/i3448.scala | 2 +- tests/run/implicitFuns.scala | 14 +- tests/run/returning.scala | 2 +- tests/run/tagless.scala | 34 ++--- 32 files changed, 330 insertions(+), 252 deletions(-) create mode 100644 docs/docs/reference/instances/implicit-conversions.md rename tests/pos/{assumeIn.scala => givenIn.scala} (83%) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e246bc74f358..b6dab96c1583 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3879,11 +3879,11 @@ object Types { def selfType(implicit ctx: Context): Type = { if (selfTypeCache == null) selfTypeCache = { - val given = cls.givenSelfType - if (!given.isValueType) appliedRef - else if (cls is Module) given + val givenSelf = cls.givenSelfType + if (!givenSelf.isValueType) appliedRef + else if (cls is Module) givenSelf else if (ctx.erasedTypes) appliedRef - else AndType(given, appliedRef) + else AndType(givenSelf, appliedRef) } selfTypeCache } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 468e09204434..c5ff58941c9e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -535,7 +535,7 @@ object Parsers { } else recur(operand()) } - else if (in.token == WITH) { + else if (in.token == GIVEN) { val top1 = reduceStack(base, top, minInfixPrec, leftAssoc = true, nme.WITHkw, isType) assert(opStack `eq` base) val app = atSpan(startOffset(top1), in.offset) { @@ -2139,7 +2139,7 @@ object Parsers { ofInstance: Boolean = false): List[List[ValDef]] = { def recur(firstClause: Boolean, nparams: Int): List[List[ValDef]] = { val initialMods = - if (in.token == WITH) { + if (in.token == GIVEN) { in.nextToken() Modifiers(Contextual | Implicit) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 06e0118daf59..ab2e28dbe25d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -180,6 +180,7 @@ object Tokens extends TokensCommon { final val ENUM = 62; enter(ENUM, "enum") final val ERASED = 63; enter(ERASED, "erased") final val INSTANCE = 64; enter(INSTANCE, "instance") + final val GIVEN = 65; enter(GIVEN, "given") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -201,7 +202,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords: TokenSet = tokenRange(IF, INSTANCE) + final val alphaKeywords: TokenSet = tokenRange(IF, GIVEN) final val symbolicKeywords: TokenSet = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens: TokenSet = tokenRange(COMMA, VIEWBOUND) final val keywords: TokenSet = alphaKeywords | symbolicKeywords diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index aaf0f2147559..e89fadac5ddc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -118,9 +118,9 @@ trait NamerContextOps { this: Context => /** The given type, unless `sym` is a constructor, in which case the * type of the constructed instance is returned */ - def effectiveResultType(sym: Symbol, typeParams: List[Symbol], given: Type): Type = + def effectiveResultType(sym: Symbol, typeParams: List[Symbol], givenTp: Type): Type = if (sym.name == nme.CONSTRUCTOR) sym.owner.typeRef.appliedTo(typeParams.map(_.typeRef)) - else given + else givenTp /** if isConstructor, make sure it has one non-implicit parameter list */ def normalizeIfConstructor(termParamss: List[List[Symbol]], isConstructor: Boolean): List[List[Symbol]] = diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index b399d22cc476..16ac1b469a2a 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -92,10 +92,11 @@ semi ::= ‘;’ | nl {nl} ``` abstract case catch class def do else enum -erased extends false final finally for if implicit -import lazy match new null object package private -protected override return super sealed then throw trait -true try type val var while with yield +erased extends false final finally for given if +implicit import instance lazy match new null object +package private protected override return super sealed then +throw trait true try type val var while +with yield : = <- => <: :> # @ ``` @@ -205,7 +206,7 @@ Catches ::= ‘catch’ Expr PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op) InfixExpr ::= PrefixExpr | InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr) - | InfixExpr ‘with’ (InfixExpr | ParArgumentExprs) + | InfixExpr ‘given’ (InfixExpr | ParArgumentExprs) PrefixExpr ::= [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr PrefixOp(expr, op) SimpleExpr ::= ‘new’ (ConstrApp [TemplateBody] | TemplateBody) New(constr | templ) | BlockExpr @@ -290,7 +291,7 @@ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | ClsParamClauses ::= {ClsParamClause} ClsParamClause ::= [nl] ‘(’ [[FunArgMods] ClsParams] ‘)’ - | ‘with’ (‘(’ ([[FunArgMods] ClsParams] ‘)’ | ContextTypes) + | ‘given’ (‘(’ ([[FunArgMods] ClsParams] ‘)’ | ContextTypes) ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param @@ -299,7 +300,7 @@ Param ::= id ‘:’ ParamType [‘=’ Expr] DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ | InstParamClause -InstParamClause ::= ‘with’ (‘(’ [DefParams] ‘)’ | ContextTypes) +InstParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ContextTypes ::= RefinedType {‘,’ RefinedType} diff --git a/docs/docs/reference/instances/context-params.md b/docs/docs/reference/instances/context-params.md index ef58b3d82ece..471c6081b86f 100644 --- a/docs/docs/reference/instances/context-params.md +++ b/docs/docs/reference/instances/context-params.md @@ -1,69 +1,68 @@ --- layout: doc-page -title: "Context Parameters and Arguments" +title: "Implicit Parameters and Arguments" --- -Context parameters are the name of a new syntax for implicit parameters that aligns definition and call syntax. Parameter definitions -and method arguments both follow a `with` connective. On the definition side, the old syntax +A new syntax for implicit parameters aligns definition and call syntax. Parameter definitions and method arguments both follow a `given` keyword. On the definition side, the old syntax ```scala def f(a: A)(implicit b: B) ``` is now expressed as ```scala -def f(a: A) with (b: B) +def f(a: A) given (b: B) ``` or, leaving out the parameter name, ```scala -def f(a: A) with B +def f(a: A) given B ``` Implicit parameters defined with the new syntax are also called _context parameters_. -They come with a matching syntax for applications: explicit arguments for context parameters are also given after a `with`. +They come with a matching syntax for applications: explicit arguments for context parameters are also written after a `given`. The following example shows shows three methods that each have a context parameter for `Ord[T]`. ```scala -def maximum[T](xs: List[T]) with Ord[T]: T = +def maximum[T](xs: List[T]) given Ord[T]: T = xs.reduceLeft((x, y) => if (x < y) y else x) -def descending[T] with (asc: Ord[T]): Ord[T] = new Ord[T] { +def descending[T] given (asc: Ord[T]): Ord[T] = new Ord[T] { def (x: T) compareTo (y: T) = asc.compareTo(y)(x) } -def minimum[T](xs: List[T]) with Ord[T] = - maximum(xs) with descending +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)`. But usually, explicit arguments for context parameters are be left out. For instance, given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:) ```scala maximum(xs) -maximum(xs) with descending -maximum(xs) with (descending with IntOrd) +maximum(xs) given descending +maximum(xs) given (descending given IntOrd) ``` -Arguments for context parameters must be given using the `with` syntax. So the expression `maximum(xs)(descending)` would give a type error. +Arguments for context parameters must use the `given` syntax. So the expression `maximum(xs)(descending)` would produce a type error. -The `with` connective is treated like an infix operator with the same precedence as other operators that start with a letter. The expression following a `with` may also be an argument list consisting of several implicit arguments separated by commas. If a tuple should be passed as a single implicit argument (probably an uncommon case), it has to be put in a pair of extra parentheses: +The `given` connective is treated like an infix operator with the same precedence as other operators that start with a letter. The expression following a `given` may also be an argument list consisting of several implicit arguments separated by commas. If a tuple should be passed as a single implicit argument (probably an uncommon case), it has to be put in a pair of extra parentheses: ```scala -def f with (x: A, y: B) -f with (a, b) +def f given (x: A, y: B) +f given (a, b) -def g with (xy: (A, B)) -g with ((a, b)) +def g given (xy: (A, B)) +g given ((a, b)) ``` Unlike existing implicit parameters, context parameters can be freely mixed with normal parameter lists. A context parameter may be followed by a normal parameter and _vice versa_. There can be several context parameter lists in a definition. Example: ```scala -def f with (u: Universe) (x: u.T) with Context = ... +def f given (u: Universe) (x: u.T) given Context = ... -instance global for Universe { type T = String ... } -instance ctx for Context { ... } +instance global of Universe { type T = String ... } +instance ctx of Context { ... } ``` Then the following calls are all valid (and normalize to the last one) ```scala f("abc") -(f with global)("abc") -f("abc") with ctx -(f with global)("abc") with ctx +(f given global)("abc") +f("abc") given ctx +(f given global)("abc") given ctx ``` Context parameters may be given either as a normal parameter list `(...)` or as a sequence of types. To distinguish the two, a leading `(` always indicates a parameter list. @@ -81,9 +80,9 @@ def summon[T] with (x: T) = x Here is the new syntax of parameters and arguments seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). ``` ClsParamClause ::= ... - | ‘with’ (‘(’ [ClsParams] ‘)’ | ContextTypes) + | ‘given’ (‘(’ [ClsParams] ‘)’ | ContextTypes) DefParamClause ::= ... | InstParamClause InfixExpr ::= ... - | InfixExpr ‘with’ (InfixExpr | ParArgumentExprs) + | InfixExpr ‘given’ (InfixExpr | ParArgumentExprs) ``` diff --git a/docs/docs/reference/instances/discussion/motivation.md b/docs/docs/reference/instances/discussion/motivation.md index 8df60ed25258..9fc239406404 100644 --- a/docs/docs/reference/instances/discussion/motivation.md +++ b/docs/docs/reference/instances/discussion/motivation.md @@ -33,13 +33,16 @@ Can implicit function types help? Implicit function types allow to abstract over ### Alternative Design -`implicit` is a modifier that gets attached to various constructs. I.e. we talk about implicit vals, defs, objects, parameters, or arguments. This conveys mechanism rather than intent. What _is_ the intent that we want to convey? Ultimately it's "trade types for terms". The programmer specifies a type and the compiler fills in the term matching that type automatically. So the concept we are after would serve to express definitions that provide the canonical _instances_ for certain types. - -The next sections elaborate such an alternative design. It consists of three proposals: - - - A proposal to replace implicit _definitions_ by [instance definitions](./instance-defs.html). - - A proposal for a [new syntax](./context-params.html) of implicit _parameters_ and their _arguments_. - - A proposal to [replace all remaining usages](./replacing-implicits.html) of `implicit` in the language. - -The first two proposals are independent of each other. The last one would work only if the first two are adopted. -A [discussion page](./discussion.html) summarizes and evaluates the proposal. +`implicit` is a modifier that gets attached to various constructs. +I.e. we talk about implicit vals, defs, objects, parameters, or arguments. +This conveys mechanism rather than intent. What _is_ the intent that we want to convey? +Ultimately it's "trade types for terms". The programmer specifies a type and the compiler +fills in the term matching that type automatically. So the concept we are after would +serve to express definitions that provide the canonical _instances_ for certain types. + +The next sections elaborate this alternative design. It consists of the following pages: + + - a proposal to replace implicit _definitions_ by [instance definitions](./instance-defs.md), + - a proposal for a [new syntax](./context-params.md) of implicit _parameters_ and their _arguments_, + - updates to the syntax for [implicit function types and closures](./implicit-function-types.md), + - a new way to express [implicit conversions](./implicit-conversions.md) as instances of a special trait, diff --git a/docs/docs/reference/instances/implicit-conversions.md b/docs/docs/reference/instances/implicit-conversions.md new file mode 100644 index 000000000000..b41bc675ec34 --- /dev/null +++ b/docs/docs/reference/instances/implicit-conversions.md @@ -0,0 +1,78 @@ +--- +layout: doc-page +title: "Implicit Conversions" +--- + +Implicit conversions are defined by 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 +instance of Conversion[String, Token] { + def apply(str: String): Token = new KeyWord(str) +} +``` +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 in the implicit scope for a an instance of +`scala.Conversion` that maps an argument of type `T` to type `S`. In the second and third +case, it looks for an instance of `scala.Conversion` 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 +instance int2Integer of Conversion[Int, java.lang.Integer] { + def apply(x: Int) = new java.lang.Integer(x) +} +``` + +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.from(statusCode) + + instance from of Conversion[String, CompletionArg] { + def apply(s: String) = CompletionArg.Error(s) + } + instance from of Conversion[Future[HttpResponse], CompletionArg] { + def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) + } + instance from of Conversion[Future[StatusCode], CompletionArg] { + def apply(code: Future[StatusCode]) = CompletionArg.Status(code) + } + } + 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/instances/implicit-function-types.md b/docs/docs/reference/instances/implicit-function-types.md index 4474711990bd..676dfaebbead 100644 --- a/docs/docs/reference/instances/implicit-function-types.md +++ b/docs/docs/reference/instances/implicit-function-types.md @@ -4,26 +4,26 @@ title: "Implicit Function Types and Closures" --- An implicit function type describes functions with implicit (context) parameters. Example: - - type Contextual[T] = Context |=> T - +```scala +type Contextual[T] = Context |=> T +``` A value of implicit function type is applied to context arguments, in the same way a method with context parameters is applied. For instance: +```scala + implicit val ctx: Context = ... - implicit val ctx: Context = ... - - def f(x: Int): Contextual[Int] = ... - - f(2) with ctx // explicit argument - f(2) // argument left implicit + def f(x: Int): Contextual[Int] = ... + f(2) given ctx // explicit argument + f(2) // argument left implicit +``` Conversely, if the expected type of an expression `E` is an implicit function type `(T_1, ..., T_n) |=> U` and `E` is not already an implicit function value, `E` is converted to an implicit function value by rewriting to - - (x_1: T1, ..., x_n: Tn) |=> E - +```scala + (x_1: T1, ..., x_n: Tn) |=> E +``` where the names `x_1`, ..., `x_n` are arbitrary. Implicit closures are written with a `|=>` connective instead of `=>` for normal closures. They differ from normal closures in two ways: @@ -31,79 +31,79 @@ with a `|=>` connective instead of `=>` for normal closures. They differ from no 2. Their types are implicit function types. For example, continuing with the previous definitions, +```scala + def g(arg: Contextual[Int]) = ... - def g(arg: Contextual[Int]) = ... - - g(22) // is expanded to g(ctx |=> 22) + g(22) // is expanded to g(ctx |=> 22) - g(f(2)) // is expanded to g(ctx |=> f(2) with ctx) - - g(ctx |=> f(22) with ctx) // is left as it is + g(f(2)) // is expanded to g(ctx |=> f(2) given ctx) + g(ctx |=> f(22) given ctx) // is left as it is +``` 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: - - table { - row { - cell("top left") - cell("top right") - } - row { - cell("bottom left") - cell("bottom right") - } +```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`: - - 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) - +```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 in terms of implicit function types to avoid the plumbing boilerplate that would otherwise be necessary. - - def table(init: Table |=> Unit) = { - instance t of Table - init - t - } - - def row(init: Row |=> Unit) with (t: Table) = { - instance r of Row - init - t.add(r) - } - - def cell(str: String) with (r: Row) = - r.add(new Cell(str)) - +```scala + def table(init: Table |=> Unit) = { + instance t of Table + init + t + } + + def row(init: Row |=> Unit) given (t: Table) = { + instance r of 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: - - table { $t: Table |=> - row { $r: Row |=> - cell("top left") with $r - cell("top right") with $r - } with $t - row { $r: Row |=> - cell("bottom left") with $r - cell("bottom right") with $r - } with $t - } - +```scala + table { $t: Table |=> + row { $r: Row |=> + cell("top left") given $r + cell("top right") given $r + } given $t + row { $r: Row |=> + cell("bottom left") given $r + cell("bottom right") given $r + } given $t + } +``` ### Reference For more info, see the [blog article](https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html), diff --git a/docs/docs/reference/instances/instance-defs.md b/docs/docs/reference/instances/instance-defs.md index 17436a508ca6..8b1c70636601 100644 --- a/docs/docs/reference/instances/instance-defs.md +++ b/docs/docs/reference/instances/instance-defs.md @@ -29,7 +29,7 @@ instance ListOrd[T: Ord] of Ord[List[T]] { } ``` Instance definitions can be seen as shorthands for what is currently expressed with implicit object and method definitions. -For instance, the definition of instance `IntOrd` above defines an implicit value of type `Ord[Int]`. It is hence equivalent +For example, the definition of instance `IntOrd` above defines an implicit value of type `Ord[Int]`. It is hence equivalent to the following implicit object definition: ```scala implicit object IntOrd extends Ord[Int] { @@ -83,7 +83,15 @@ If the name of an instance is missing, the compiler will synthesize a name from the type in the of clause, or, if that is missing, from the first defined extension method. -## Conditional Instances +**Aside: ** Why anonymous instances? + + - It avoids clutter, relieving the programmer from having to invent names that are never referred to. + Usually the invented names are either meaning less (e.g. `ev1`), or they just rephrase the implemented type. + - It gives a systematic foundation for synthesized instance definitions, such as those coming from a `derives` clause. + - It achieves a uniform principle that the name of an implicit is always optional, no matter + whether the implicit is an instance definition or an implicit parameter. + +## Conditional Implicits An instance definition can depend on another instance being defined. Example: ```scala @@ -91,20 +99,23 @@ trait Conversion[-From, +To] { def apply(x: From): To } -instance [S, T] with (c: Conversion[S, T]) of Conversion[List[S], List[T]] { +instance [S, T] given (c: Conversion[S, T]) of Conversion[List[S], List[T]] { def convert(x: List[From]): List[To] = x.map(c.apply) } ``` This defines an implicit conversion from `List[S]` to `List[T]` provided there is an implicit conversion from `S` to `T`. -The `with` clause instance defines required instances. The instance of `Conversion[List[From], List[To]]` above is defined only if an instance of `Conversion[From, To]` exists. +The `given` clause defines required instances. The `Conversion[List[From], List[To]]` instance above +is defined only if a `Conversion[From, To]` instance exists. -Context bounds in instance definitions also translate to implicit parameters, and therefore they can be represented alternatively as with clauses. For instance, here is an equivalent definition of the `ListOrd` instance: +Context bounds in instance definitions also translate to implicit parameters, +and therefore they can be represented alternatively as with clauses. For example, +here is an equivalent definition of the `ListOrd` instance: ```scala -instance ListOrd[T] with (ord: Ord[T]) of List[Ord[T]] { ... } +instance ListOrd[T] given (ord: Ord[T]) of List[Ord[T]] { ... } ``` -The name of a parameter in a `with` clause can also be left out, as shown in the following variant of `ListOrd`: +The name of a parameter in a `given` clause can also be left out, as shown in the following variant of `ListOrd`: ```scala -instance ListOrd[T] with Ord[T] of List[Ord[T]] { ... } +instance ListOrd[T] given Ord[T] of List[Ord[T]] { ... } ``` As usual one can then infer to implicit parameter only indirectly, by passing it as implicit argument to another function. @@ -187,9 +198,10 @@ TmplDef ::= ... | ‘instance’ InstanceDef InstanceDef ::= [id] InstanceParams InstanceBody InstanceParams ::= [DefTypeParamClause] {InstParamClause} -InstParamClause ::= ‘with’ (‘(’ [DefParams] ‘)’ | ContextTypes) +InstParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) InstanceBody ::= [‘of’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] | ‘of’ Type ‘=’ Expr ContextTypes ::= RefinedType {‘,’ RefinedType} ``` -The identifier `id` can be omitted only if either the `of` part or the template body is present. If the `of` part is missing, the template body must define at least one extension method. +The identifier `id` can be omitted only if either the `of` part or the template body is present. +If the `of` part is missing, the template body must define at least one extension method. diff --git a/docs/docs/reference/instances/replacing-implicits.md b/docs/docs/reference/instances/replacing-implicits.md index 93bf50c1b75f..b40738ca1b3c 100644 --- a/docs/docs/reference/instances/replacing-implicits.md +++ b/docs/docs/reference/instances/replacing-implicits.md @@ -3,68 +3,54 @@ layout: doc-page title: "Replacing Implicits" --- -The previous pages describe a new, high-level syntax for implicit definitions, parameters, function literals, and function types. With the exception of context parameters - -These idioms can by-and-large be mapped to existing implicits. The only exception concerns context parameters which give genuinely more freedom in the way parameters can be organized. The new idioms are preferable to existing implicits since they are both more concise and better behaved. The better expressiveness comes at a price, however, since it leaves us with two related constructs: new style instance definitions and context parameters and traditional implicits. This page discusses what would be needed to get rid of `implicit` entirely. - -The contents of this page are more tentative than the ones of the previous pages. The concepts described in the previous pages are useful independently whether the changes on this page are adopted. +The previous pages describe a new, high-level syntax for implicit definitions, parameters, function literals, and function types. +These idioms can by-and-large be mapped to existing implicits. The only exception concerns context parameters which give genuinely more freedom in the way parameters can be organized. The new idioms are preferable to existing implicits since they are both more concise and better behaved. The better expressiveness comes at a price, however, since it leaves us with both the new and the old way to express implicits. This page discusses what would be needed to get rid of all existing uses of `implicit` as a modifier. The current Dotty implementation implements the new concepts described on this page (alias instances and the summon method), but it does not remove any of the old-style implicit constructs. It cannot do this since support for old-style implicits is an essential part of the common language subset of Scala 2 and Scala 3.0. Any deprecation and subsequent removal of these constructs would have to come later, in a version following 3.0. The `implicit` modifier can be removed from the language at the end of this development, if it happens. -## Alias Instances +## Add: Alias Instances -An alias instance creates an instance that is equal to some expression. -``` -instance ctx of ExecutionContext = currentThreadPool().context -``` -Here, we create an instance `ctx` of type `ExecutionContext` that resolves to the -right hand side `currentThreadPool().context`. Each time an instance of `ExecutionContext` -is demanded, the result of evaluating the right-hand side expression is returned. The instance definition is equivalent to the following implicit definition: +To replace implicit vals and defs (both abstract and concrete), we need one way to +"lift" an existing value to become an implicit instance for a type. This is achieved +by an alias instance, which creates an instance that is equal to some expression. +```scala +implicit ctx for ExecutionContext = currentThreadPool().context ``` +Here, we create an implicit `ctx` of type `ExecutionContext` that resolves to the +right hand side `currentThreadPool().context`. Each time an implicit of `ExecutionContext` +is demanded, the result of evaluating the right-hand side expression is returned. The definition is equivalent to the following implicit definition in Scala 2: +```scala final implicit def ctx: ExecutionContext = currentThreadPool().context ``` -Alias instances may be anonymous, e.g. -``` -instance of Position = enclosingTree.position +Implicit aliases may be anonymous, e.g. +```scala +implicit for Position = enclosingTree.position ``` -An alias instance can have type and context parameters just like any other instance definition, but it can only implement a single type. +An implicit alias can have type and context parameters just like any other implicit definition, but it can only implement a single type. -## Replaced: Implicit Conversions +## Drop: Implicit Conversions Implicit conversions using the `implicit def` syntax are no longer needed, since they -can be expressed as 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 -instance of Conversion[String, Token] { - def apply(str: String): Token = new KeyWord(str) -} -``` -The fact that this syntax is more verbose than simple implicit defs could be a welcome side effect since it might dampen any over-enthusiasm for defining implicit conversions. +can be expressed as instances of the `scala.Conversion` class. -## Dropped: Implicit Classes +## Drop: Implicit Classes Most use cases of implicit classes are already covered by extension methods. For the others, one can always fall back to a pair of a regular class and a `Conversion` instance. -## Summoning an Instance +## Drop: Implicit As A Modifier -Besides `implicit`, there is also `implicitly`, a method defined in `Predef` that computes an implicit value for a given type. We propose to rename this operation to `summon`. So `summon[T]` summons an instance of `T`, in the same way as `implicitly[T]` did. The definition of `summon` is straightforward: -```scala -def summon[T] with (x: T) = x -``` + - Old-style implicit parameters are replaced by `given` parameters. + - Implicit function types `implicit T => U` are written `T |=> U` + - Implicit closures `implicit x => e` are written `x |=> e` + - All remaining implicit `val` and `def` definition are replaced by normal + `val` or `def` definitions and implicit aliases.= ## Syntax The syntax changes for this page are summarized as follows: ``` InstanceBody ::= ... - | ‘of’ Type ‘=’ Expr + | ‘for’ Type ‘=’ Expr ``` In addition, the `implicit` modifier is removed together with all [productions]((http://dotty.epfl.ch/docs/internals/syntax.html) that reference it. - -## Further Reading - -Here is the [original proposal](./discussion/motivation.html) that makes the case for the changes described in these pages. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 6672df319fc3..895beb889a1c 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -77,10 +77,8 @@ sidebar: url: docs/reference/instances/implicit-function-types.html - title: Implicit Conversions url: docs/reference/instances/implicit-conversions.html - - title: Replacing Implicits + - title: ReplacedImplicits url: docs/reference/instances/replacing-implicits.html - - title: Discussion - url: docs/reference/instances/discussion.html - title: Other Changed Features subsection: - title: Volatile Lazy Vals diff --git a/tests/neg/contextual-params.scala b/tests/neg/contextual-params.scala index 2d44284955d0..d42305967f8a 100644 --- a/tests/neg/contextual-params.scala +++ b/tests/neg/contextual-params.scala @@ -2,21 +2,21 @@ object Test { case class C(x: Int) - def f(x: Int) with (c: C) = x + c.x + def f(x: Int) given (c: C) = x + c.x - def g(x: Int) with (c: C) (y: Int) = x + c.x + y + def g(x: Int) given (c: C) (y: Int) = x + c.x + y implicit object C extends C(11) f(1) - f(1) with C - f with 2 // error + f(1) given C + f given 2 // error f(1)(C) // error g(1)(2) // OK - (g(1) with C)(2) // OK - g(1) with 2 // error - g(1) with C with 2 // error + (g(1) given C)(2) // OK + g(1) given 2 // error + g(1) given C given 2 // error g(1)(C)(2) // error - g(1)(C) with 2 // error + g(1)(C) given 2 // error } \ No newline at end of file diff --git a/tests/neg/i2514.scala b/tests/neg/i2514.scala index c05c4b434f42..e1d58a87d20f 100644 --- a/tests/neg/i2514.scala +++ b/tests/neg/i2514.scala @@ -1,7 +1,7 @@ object Foo { def foo(): Int = { val f: implicit Int => Int = (implicit x: Int) => 2 * x // error // error - f with 2 + f given 2 } val f = (implicit x: Int) => x // error // error diff --git a/tests/neg/i2514a.scala b/tests/neg/i2514a.scala index 59c954f31a25..0528fcd20ef1 100644 --- a/tests/neg/i2514a.scala +++ b/tests/neg/i2514a.scala @@ -1,7 +1,7 @@ object Foo { def foo(): Int = { val f: Int |=> Int = (x: Int) |=> 2 * x - f with 2 + f given 2 } val f = implicit (x: Int) => x diff --git a/tests/neg/i2960.scala b/tests/neg/i2960.scala index a134d3721350..04a77946e61c 100644 --- a/tests/neg/i2960.scala +++ b/tests/neg/i2960.scala @@ -24,7 +24,7 @@ class Tag(val name: String, def apply[U](f: Tag |=> U)(implicit t: Tag = null): this.type = { if(t != null) t.children += this - f with this + f given this this } } diff --git a/tests/pos/case-getters.scala b/tests/pos/case-getters.scala index dc6476e10674..401972808507 100644 --- a/tests/pos/case-getters.scala +++ b/tests/pos/case-getters.scala @@ -3,6 +3,6 @@ object Test { val f = Foo(1, (i: Int) |=> i) val fx1: 1 = f.x val fx2: 1 = f._1 - val fy1: Int = f.y with 1 - val fy2: Int = f._2 with 1 + val fy1: Int = f.y given 1 + val fy2: Int = f._2 given 1 } diff --git a/tests/pos/depfuntype.scala b/tests/pos/depfuntype.scala index c31978af8536..356d3e89b65d 100644 --- a/tests/pos/depfuntype.scala +++ b/tests/pos/depfuntype.scala @@ -25,9 +25,9 @@ object Test { val ifun: IDF = implicitly[C].m - val u = ifun with c + val u = ifun given c val u1: Int = u - val v = ifun with d + val v = ifun given d val v1: d.M = v } diff --git a/tests/pos/eff-compose.scala b/tests/pos/eff-compose.scala index 80e43b70d199..b719cedf2431 100644 --- a/tests/pos/eff-compose.scala +++ b/tests/pos/eff-compose.scala @@ -36,7 +36,7 @@ object Test { } } - implicit def combine[E1 <: Effect, E2 <: Effect] with (x: E1, y: E2): E1 & E2 = ??? + implicit def combine[E1 <: Effect, E2 <: Effect] given (x: E1, y: E2): E1 & E2 = ??? // def compose(f: A => B)(g: B => C)(x: A): C def compose[A, B, C, E1 <: Effect, E2 <: Effect] diff --git a/tests/pos/assumeIn.scala b/tests/pos/givenIn.scala similarity index 83% rename from tests/pos/assumeIn.scala rename to tests/pos/givenIn.scala index 6c367f826b2c..6b96900fa4f8 100644 --- a/tests/pos/assumeIn.scala +++ b/tests/pos/givenIn.scala @@ -2,15 +2,15 @@ object Test { import scala.compiletime.constValue class Context { - inline def assumeIn[T](op: => Context |=> T) = { + inline def givenIn[T](op: => Context |=> T) = { instance of Context = this op } } def ctx: Context = new Context - def g with Context = () - ctx.assumeIn(g) + def g given Context = () + ctx.givenIn(g) /* The last three statements shoudl generate the following code: diff --git a/tests/pos/i2146.scala b/tests/pos/i2146.scala index 674094e9914b..2c920b15d5fa 100644 --- a/tests/pos/i2146.scala +++ b/tests/pos/i2146.scala @@ -16,8 +16,8 @@ object Test { def main(args: Array[String]) = { println(foo[A, B]) - println(foo[A, B] with a) - println(foo with a with b) + println(foo[A, B] given a) + println(foo given a given b) val s: A |=> A = simple[A] println(s) val x0: A |=> B |=> (A, B) = foo[A, B] @@ -26,7 +26,7 @@ object Test { println(x1) println(bar[A, B]) - println(bar[A, B] with a) - println(bar with a with b) + println(bar[A, B] given a) + println(bar given a given b) } } diff --git a/tests/pos/i3692.scala b/tests/pos/i3692.scala index 2911f548f56f..77b68bf27c5b 100644 --- a/tests/pos/i3692.scala +++ b/tests/pos/i3692.scala @@ -7,8 +7,8 @@ object Main { def main(args: Array[String]): Unit = { val choose: (c: C) |=> Set[Int] = Set.empty - val b0: (C) => Set[Int] = choose with _ - val b1: (c: C) => Set[Int] = choose with _ + val b0: (C) => Set[Int] = choose given _ + val b1: (c: C) => Set[Int] = choose given _ def applyF(f: (c: C) => Set[Int]) = f(new C{type T=Int}) //applyF(choose) } diff --git a/tests/pos/i4725.scala b/tests/pos/i4725.scala index 46a9a7ecaee8..72fca9940310 100644 --- a/tests/pos/i4725.scala +++ b/tests/pos/i4725.scala @@ -1,8 +1,8 @@ object Test1 { trait T[A] - def foo[S[_], A] with (ev: T[A] |=> T[S[A]]): Unit = () - implicit def bar[A] with (ev: T[A]): T[List[A]] = ??? + def foo[S[_], A] given (ev: T[A] |=> T[S[A]]): Unit = () + implicit def bar[A] given (ev: T[A]): T[List[A]] = ??? foo[List, Int] } @@ -11,8 +11,8 @@ object Test2 { trait T trait S - def foo with (ev: T |=> S): Unit = () - implicit def bar with (ev: T): S = ??? + def foo given (ev: T |=> S): Unit = () + implicit def bar given (ev: T): S = ??? foo } diff --git a/tests/pos/inline-apply.scala b/tests/pos/inline-apply.scala index 60c024af2227..1e58b6bceb41 100644 --- a/tests/pos/inline-apply.scala +++ b/tests/pos/inline-apply.scala @@ -3,7 +3,7 @@ class Context object Test { def transform()(implicit ctx: Context) = { - inline def withLocalOwner[T](op: Context |=> T) = op with ctx + inline def withLocalOwner[T](op: Context |=> T) = op given ctx withLocalOwner { ctx |=> () } diff --git a/tests/pos/reference/instances.scala b/tests/pos/reference/instances.scala index 3618b5a257a9..6606f9518a43 100644 --- a/tests/pos/reference/instances.scala +++ b/tests/pos/reference/instances.scala @@ -37,7 +37,7 @@ object Instances extends Common { if (x < y) -1 else if (x > y) +1 else 0 } - instance ListOrd[T] with Ord[T] of Ord[List[T]] { + instance ListOrd[T] given Ord[T] of Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -73,21 +73,21 @@ object Instances extends Common { ctx => x } - def maximum[T](xs: List[T]) with Ord[T]: T = + def maximum[T](xs: List[T]) given Ord[T]: T = xs.reduceLeft((x, y) => if (x < y) y else x) - def descending[T] with (asc: Ord[T]): Ord[T] = new Ord[T] { + def descending[T] given (asc: Ord[T]): Ord[T] = new Ord[T] { def (x: T) compareTo (y: T) = asc.compareTo(y)(x) } - def minimum[T](xs: List[T]) with Ord[T] = - maximum(xs) with descending + def minimum[T](xs: List[T]) given Ord[T] = + maximum(xs) given descending def test(): Unit = { val xs = List(1, 2, 3) println(maximum(xs)) - println(maximum(xs) with descending) - println(maximum(xs) with (descending with IntOrd)) + println(maximum(xs) given descending) + println(maximum(xs) given (descending given IntOrd)) println(minimum(xs)) } @@ -116,7 +116,7 @@ object Instances extends Common { class D[T] - class C with (ctx: Context) { + class C given (ctx: Context) { def f() = { locally { instance of Context = this.ctx @@ -132,7 +132,7 @@ object Instances extends Common { println(summon[D[Int]]) } locally { - instance with Context of D[Int] + instance given Context of D[Int] println(summon[D[Int]]) } } @@ -155,11 +155,11 @@ object PostConditions { def (x: WrappedResult[T]) unwrap[T]: T = x } - def result[T] with (wrapped: WrappedResult[T]): T = wrapped.unwrap + def result[T] given (wrapped: WrappedResult[T]): T = wrapped.unwrap instance { def (x: T) ensuring[T] (condition: WrappedResult[T] |=> Boolean): T = { - assert(condition with WrappedResult(x)) + assert(condition given WrappedResult(x)) x } } @@ -193,7 +193,7 @@ object AnonymousInstances extends Common { def (xs: List[T]) second[T] = xs.tail.head } - instance [From, To] with (c: Convertible[From, To]) of Convertible[List[From], List[To]] { + instance [From, To] given (c: Convertible[From, To]) of Convertible[List[From], List[To]] { def (x: List[From]) convert: List[To] = x.map(c.convert) } diff --git a/tests/run/config.scala b/tests/run/config.scala index 7d30001be36c..7710e0d5a2af 100644 --- a/tests/run/config.scala +++ b/tests/run/config.scala @@ -29,8 +29,8 @@ object Imperative { ).onError(None) def main(args: Array[String]) = { - println(readPerson with Config("John Doe", 20)) - println(readPerson with Config("Incognito", 99)) + println(readPerson given Config("John Doe", 20)) + println(readPerson given Config("Incognito", 99)) } } @@ -56,7 +56,7 @@ object Exceptions { class OnError[T](op: Possibly[T]) { def onError(fallback: => T): T = - try op with (new CanThrow) + try op given (new CanThrow) catch { case ex: E => fallback } } } @@ -85,8 +85,8 @@ object Test extends App { val config1 = Config("John Doe", 20) val config2 = Config("Incognito", 99) - println(readPerson with config1) - println(readPerson with config2) + println(readPerson given config1) + println(readPerson given config2) } object OptionTest extends App { diff --git a/tests/run/erased-23.scala b/tests/run/erased-23.scala index e61a0ad4a981..17b32d344b78 100644 --- a/tests/run/erased-23.scala +++ b/tests/run/erased-23.scala @@ -8,6 +8,6 @@ object Test { } def fun(f: erased Int |=> String): String = { - f with 35 + f given 35 } } diff --git a/tests/run/i2939.scala b/tests/run/i2939.scala index 1e2937617bde..9c345067031d 100644 --- a/tests/run/i2939.scala +++ b/tests/run/i2939.scala @@ -8,7 +8,7 @@ class Tag(val name: String, val buffer: Buffer[Tag] = ArrayBuffer()) { } def apply[U](f: Tag |=> U)(implicit tag: Tag = null): this.type = { - f with this + f given this if(tag != null) tag.buffer += this this } diff --git a/tests/run/i3448.scala b/tests/run/i3448.scala index 6905c1cbcdbf..e25fabbd2c26 100644 --- a/tests/run/i3448.scala +++ b/tests/run/i3448.scala @@ -8,6 +8,6 @@ object Test extends App { val xs0: List[IF[Int]] = List(_ |=> x) val xs: List[IF[Int]] = List(x) val ys: IF[List[Int]] = xs.map(x => x) - val zs = ys with C(22) + val zs = ys given C(22) assert(zs == List(22)) } diff --git a/tests/run/implicitFuns.scala b/tests/run/implicitFuns.scala index 30893ed544c9..0398463d6cc0 100644 --- a/tests/run/implicitFuns.scala +++ b/tests/run/implicitFuns.scala @@ -13,14 +13,14 @@ object Test { val xx: (String, Int) |=> Int = (x: String, y: Int) |=> x.length + y - val y: String => Boolean = x with _ + val y: String => Boolean = x given _ object nested { implicit val empty: String = "" assert(!x) } - val yy: (String, Int) => Any = xx with (_, _) + val yy: (String, Int) => Any = xx given (_, _) val z1: String |=> Boolean = implicitly[String].length >= 2 assert(z1) @@ -40,7 +40,7 @@ object Test { val z4: GenericImplicit[String] = implicitly[String].length >= 2 assert(z4) - val b = x with "hello" + val b = x given "hello" val b1: Boolean = b @@ -48,7 +48,7 @@ object Test { val bi1: Boolean = bi - val c = xx with ("hh", 22) + val c = xx given ("hh", 22) val c1: Int = c @@ -56,7 +56,7 @@ object Test { def foo(s: String): Stringly[Int] = 42 - (if ("".isEmpty) foo("") else foo("")).apply with "" + (if ("".isEmpty) foo("") else foo("")).apply given "" } } @@ -81,11 +81,11 @@ object Contextual { def ctx: Ctx[Context] = implicitly[Context] def compile(s: String): Ctx[Boolean] = - (runOn(new java.io.File(s)) with ctx.withBinding(Source, s)) >= 0 + (runOn(new java.io.File(s)) given ctx.withBinding(Source, s)) >= 0 def runOn(f: java.io.File): Ctx[Int] = { val options = List("-verbose", "-explaintypes") - process(f).apply with ctx.withBinding(Options, options) + process(f).apply given ctx.withBinding(Options, options) } def process(f: java.io.File): Ctx[Int] = diff --git a/tests/run/returning.scala b/tests/run/returning.scala index 28d65ed7056d..f7ce747267b6 100644 --- a/tests/run/returning.scala +++ b/tests/run/returning.scala @@ -16,7 +16,7 @@ object NonLocalReturns { def returning[T](op: ReturnThrowable[T] |=> T): T = { val returner = new ReturnThrowable[T] - try op with returner + try op given returner catch { case ex: ReturnThrowable[_] => if (ex `eq` returner) ex.result.asInstanceOf[T] else throw ex diff --git a/tests/run/tagless.scala b/tests/run/tagless.scala index d59b091b39bb..41e1e4f87fbd 100644 --- a/tests/run/tagless.scala +++ b/tests/run/tagless.scala @@ -24,19 +24,19 @@ object Test extends App { } // An example tree - def tf0[T] with (e: Exp[T]): T = + def tf0[T] given (e: Exp[T]): T = e.add(e.lit(8), e.neg(e.add(e.lit(1), e.lit(2)))) // Typeclass-style Exp syntax object ExpSyntax { - def lit[T](i: Int) with (e: Exp[T]): T = e.lit(i) - def neg[T](t: T) with (e: Exp[T]): T = e.neg(t) - def add[T](l: T, r: T) with (e: Exp[T]): T = e.add(l, r) + def lit[T](i: Int) given (e: Exp[T]): T = e.lit(i) + def neg[T](t: T) given (e: Exp[T]): T = e.neg(t) + def add[T](l: T, r: T) given (e: Exp[T]): T = e.add(l, r) } import ExpSyntax._ // It's safe to always have these in scope // Another tree - def tf1[T] with Exp[T]: T = + def tf1[T] given Exp[T]: T = add(lit(8), neg(add(lit(1), lit(2)))) // Base operations as typeclasses @@ -60,7 +60,7 @@ object Test extends App { def mul(l: T, r: T): T } object MultSyntax { - def mul[T](l: T, r: T) with (e: Mult[T]): T = e.mul(l, r) + def mul[T](l: T, r: T) given (e: Mult[T]): T = e.mul(l, r) } import MultSyntax._ @@ -106,7 +106,7 @@ object Test extends App { object CanThrow { private class Exc(msg: String) extends Exception(msg) - def _throw(msg: String) with CanThrow: Nothing = throw new Exc(msg) + def _throw(msg: String) given CanThrow: Nothing = throw new Exc(msg) def _try[T](op: CanThrow |=> T)(handler: String => T): T = { instance of CanThrow try op @@ -138,7 +138,7 @@ object Test extends App { show(readInt("2")) show(readInt("X")) - def fromTree[T](t: Tree) with Exp[T]: Maybe[T] = t match { + def fromTree[T](t: Tree) given Exp[T]: Maybe[T] = t match { case Node("Lit", Leaf(n)) => lit(readInt(n)) case Node("Neg", t) => neg(fromTree(t)) case Node("Add", l , r) => add(fromTree(l), fromTree(r)) @@ -150,18 +150,18 @@ object Test extends App { show(fromTree[Tree](tf1Tree)) trait Wrapped { - def value[T] with Exp[T]: T + def value[T] given Exp[T]: T } instance of Exp[Wrapped] { def lit(i: Int) = new Wrapped { - def value[T] with (e: Exp[T]): T = e.lit(i) + def value[T] given (e: Exp[T]): T = e.lit(i) } def neg(t: Wrapped) = new Wrapped { - def value[T] with (e: Exp[T]): T = e.neg(t.value) + def value[T] given (e: Exp[T]): T = e.neg(t.value) } def add(l: Wrapped, r: Wrapped) = new Wrapped { - def value[T] with (e: Exp[T]): T = e.add(l.value, r.value) + def value[T] given (e: Exp[T]): T = e.add(l.value, r.value) } } @@ -170,7 +170,7 @@ object Test extends App { s"${t.value[Int]}\n${t.value[String]}" } - def fromTreeExt[T](recur: => Tree => Maybe[T]) with Exp[T]: Tree => Maybe[T] = { + def fromTreeExt[T](recur: => Tree => Maybe[T]) given Exp[T]: Tree => Maybe[T] = { case Node("Lit", Leaf(n)) => lit(readInt(n)) case Node("Neg", t) => neg(recur(t)) case Node("Add", l , r) => add(recur(l), recur(r)) @@ -181,7 +181,7 @@ object Test extends App { def fromTree2[T: Exp](t: Tree): Maybe[T] = fix(fromTreeExt[T])(t) - def fromTreeExt2[T](recur: => Tree => Maybe[T]) with Exp[T], Mult[T]: Tree => Maybe[T] = { + def fromTreeExt2[T](recur: => Tree => Maybe[T]) given Exp[T], Mult[T]: Tree => Maybe[T] = { case Node("Mult", l , r) => mul(recur(l), recur(r)) case t => fromTreeExt(recur)(t) } @@ -196,7 +196,7 @@ object Test extends App { // Added operation: negation pushdown enum NCtx { case Pos, Neg } - instance [T] with (e: Exp[T]) of Exp[NCtx => T] { + instance [T] given (e: Exp[T]) of Exp[NCtx => T] { import NCtx._ def lit(i: Int) = { case Pos => e.lit(i) @@ -216,7 +216,7 @@ object Test extends App { println(pushNeg(tf1[NCtx => String])) println(pushNeg(pushNeg(pushNeg(tf1))): String) - instance [T] with (e: Mult[T]) of Mult[NCtx => T] { + instance [T] given (e: Mult[T]) of Mult[NCtx => T] { import NCtx._ def mul(l: NCtx => T, r: NCtx => T): NCtx => T = { case Pos => e.mul(l(Pos), r(Pos)) @@ -237,7 +237,7 @@ object Test extends App { } // Going from ADT encoding to type class encoding - def finalize[T](i: IExp) with (e: Exp[T]): T = i match { + def finalize[T](i: IExp) given (e: Exp[T]): T = i match { case Lit(l) => e.lit(l) case Neg(n) => e.neg(finalize[T](n)) case Add(l, r) => e.add(finalize[T](l), finalize[T](r)) From 8201ae1add173cff6123f4624d1f5a0b01eb2603 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 Jan 2019 11:48:11 +0100 Subject: [PATCH 02/16] Disallow empty implicit parameter sections Previously, `given ()` was legal, but it serves no purpose and only complicates things. --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 9 +++++---- compiler/src/dotty/tools/dotc/typer/Namer.scala | 8 +++----- .../{contextual-params.scala => implicit-params.scala} | 2 ++ 3 files changed, 10 insertions(+), 9 deletions(-) rename tests/neg/{contextual-params.scala => implicit-params.scala} (90%) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index c5ff58941c9e..9cccbaf2f678 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2038,7 +2038,7 @@ object Parsers { * ClsParams ::= ClsParam {`' ClsParam} * ClsParam ::= {Annotation} [{Modifier} (`val' | `var') | `inline'] Param * DefParamClause ::= [nl] `(' [FunArgMods] [DefParams] ')' | InstParamClause - * InstParamClause ::= ‘with’ (‘(’ [DefParams] ‘)’ | ContextTypes) + * InstParamClause ::= ‘given’ (‘(’ DefParams ‘)’ | ContextTypes) * ContextTypes ::= RefinedType {`,' RefinedType} * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param @@ -2109,10 +2109,11 @@ object Parsers { // begin paramClause inParens { - if (in.token == RPAREN && !prefix) Nil + val isContextual = impliedMods.is(Contextual) + if (in.token == RPAREN && !prefix && !isContextual) Nil else { def funArgMods(mods: Modifiers): Modifiers = - if (in.token == IMPLICIT && !mods.is(Contextual)) + if (in.token == IMPLICIT && !isContextual) funArgMods(addMod(mods, atSpan(accept(IMPLICIT)) { Mod.Implicit() })) else if (in.token == ERASED) funArgMods(addMod(mods, atSpan(accept(ERASED)) { Mod.Erased() })) @@ -2148,7 +2149,7 @@ object Parsers { newLineOptWhenFollowedBy(LPAREN) if (in.token == LPAREN) { if (ofInstance && !isContextual) - syntaxError(em"parameters of instance definitions must come after `with'") + syntaxError(em"parameters of instance definitions must come after `given'") val params = paramClause( ofClass = ofClass, ofCaseClass = ofCaseClass, diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e89fadac5ddc..092969fe43ee 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -131,12 +131,11 @@ trait NamerContextOps { this: Context => termParamss /** The method type corresponding to given parameters and result type */ - def methodType(typeParams: List[Symbol], valueParamss: List[List[Symbol]], resultType: Type, - isJava: Boolean = false, isInstance: Boolean = false)(implicit ctx: Context): Type = { + def methodType(typeParams: List[Symbol], valueParamss: List[List[Symbol]], resultType: Type, isJava: Boolean = false)(implicit ctx: Context): Type = { val monotpe = (valueParamss :\ resultType) { (params, resultType) => val (isImplicit, isErased, isContextual) = - if (params.isEmpty) (isInstance, false, false) + if (params.isEmpty) (false, false, false) else (params.head is Implicit, params.head is Erased, params.head.is(Contextual)) val make = MethodType.maker(isJava = isJava, isImplicit = isImplicit, isErased = isErased, isContextual = isContextual) if (isJava) @@ -1301,8 +1300,7 @@ class Namer { typer: Typer => val termParamss = ctx.normalizeIfConstructor(vparamss.nestedMap(symbolOfTree), isConstructor) def wrapMethType(restpe: Type): Type = { instantiateDependent(restpe, typeParams, termParamss) - ctx.methodType(tparams map symbolOfTree, termParamss, restpe, - isJava = ddef.mods is JavaDefined, isInstance = ddef.mods.hasMod(classOf[Mod.Instance])) + ctx.methodType(tparams map symbolOfTree, termParamss, restpe, isJava = ddef.mods is JavaDefined) } if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol diff --git a/tests/neg/contextual-params.scala b/tests/neg/implicit-params.scala similarity index 90% rename from tests/neg/contextual-params.scala rename to tests/neg/implicit-params.scala index d42305967f8a..ec38f07fa2aa 100644 --- a/tests/neg/contextual-params.scala +++ b/tests/neg/implicit-params.scala @@ -6,6 +6,8 @@ object Test { def g(x: Int) given (c: C) (y: Int) = x + c.x + y + def h(x: Int) given () = x // error + implicit object C extends C(11) f(1) From 5dd0a11264c57cb6936b6cce20156ae9ddc933c6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 Jan 2019 11:49:53 +0100 Subject: [PATCH 03/16] Avoid spurious error spans Parser.syntaxError contains a tweak that underlines the whole span of the current token, if it has a name. Tho make this work reliably we need to reset the name to null when reading a new token, since not every token sets a name. --- compiler/src/dotty/tools/dotc/parsing/Scanners.scala | 1 + tests/neg/trailingCommas.scala | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index ec5f0bcf4181..28da40700013 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -407,6 +407,7 @@ object Scanners { */ protected final def fetchToken(): Unit = { offset = charOffset - 1 + name = null (ch: @switch) match { case ' ' | '\t' | CR | LF | FF => nextChar() diff --git a/tests/neg/trailingCommas.scala b/tests/neg/trailingCommas.scala index 356f735ce656..ad5a0f3ea942 100644 --- a/tests/neg/trailingCommas.scala +++ b/tests/neg/trailingCommas.scala @@ -18,7 +18,7 @@ trait SimpleExpr { (23, "bar", ) } // error trait TypeArgs { def f: ValidGeneric[Int, String, ] } // error trait TypeParamClause { type C[A, B, ] } // error -trait FunTypeParamClause { def f[A, B, ] } // error +trait FunTypeParamClause { def f[A, B, ] } // error // error trait SimpleType { def f: (Int, String, ) } // error trait FunctionArgTypes { def f: (Int, String, ) => Boolean } // error From 2baba5b0d7686f3e92ad2e1b74f851f8bc043d4e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 Jan 2019 15:55:38 +0100 Subject: [PATCH 04/16] Change syntax of implicit function types and closures It's `given A => B` instead of `A |=> B`. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 + .../dotty/tools/dotc/core/Definitions.scala | 4 +- .../dotty/tools/dotc/parsing/Parsers.scala | 87 ++++++++----------- .../src/dotty/tools/dotc/parsing/Tokens.scala | 12 ++- .../tools/dotc/printing/RefinedPrinter.scala | 17 ++-- .../dotty/tools/dotc/typer/EtaExpansion.scala | 4 +- .../src/dotty/tools/dotc/typer/Typer.scala | 5 +- docs/docs/internals/syntax.md | 10 +-- .../instances/implicit-function-types-spec.md | 14 +-- .../instances/implicit-function-types.md | 24 ++--- .../instances/replacing-implicits.md | 4 +- .../languageserver/util/PositionContext.scala | 2 +- .../languageserver/util/actions/Action.scala | 2 +- tests/neg/i2006.scala | 4 +- tests/neg/i2146.scala | 4 +- tests/neg/i2514a.scala | 4 +- tests/neg/i2642.scala | 4 +- tests/neg/i2960.scala | 2 +- tests/neg/i4196.scala | 2 +- tests/neg/i4611a.scala | 6 +- tests/neg/i4611b.scala | 2 +- tests/neg/implicit-shadowing.scala | 4 +- tests/pos/Orderings.scala | 2 +- tests/pos/case-getters.scala | 4 +- tests/pos/depfuntype.scala | 2 +- tests/pos/eff-compose.scala | 8 +- tests/pos/givenIn.scala | 2 +- tests/pos/ho-implicits.scala | 4 +- tests/pos/i2146.scala | 12 +-- tests/pos/i2278.scala | 2 +- tests/pos/i2671.scala | 4 +- tests/pos/i2749.scala | 12 +-- tests/pos/i3692.scala | 2 +- tests/pos/i4125.scala | 2 +- tests/pos/i4196.scala | 2 +- tests/pos/i4203.scala | 2 +- tests/pos/i4725.scala | 4 +- tests/pos/i4753.scala | 6 +- tests/pos/i4753b.scala | 4 +- tests/pos/implicit-dep.scala | 2 +- tests/pos/implicitFuns.scala | 6 +- tests/pos/inline-apply.scala | 4 +- tests/pos/reference/instances.scala | 8 +- tests/run/builder.scala | 4 +- tests/run/config.scala | 4 +- tests/run/eff-dependent.scala | 10 +-- tests/run/erased-23.check | 1 + tests/run/erased-23.scala | 13 ++- tests/run/i2642.scala | 2 +- tests/run/i2939.scala | 2 +- tests/run/i3448.scala | 4 +- tests/run/implicit-shortcut-bridge.scala | 10 +-- tests/run/implicitFunctionXXL.scala | 4 +- tests/run/implicitFuns.scala | 22 ++--- tests/run/implicitFuns2.scala | 14 +-- tests/run/implicitShortcut/Base_1.scala | 2 +- tests/run/implicitShortcut/Derived_2.scala | 2 +- tests/run/returning.scala | 2 +- tests/run/tagless.scala | 6 +- .../Macro_1.scala | 2 +- .../Macro_1.scala | 7 +- 61 files changed, 210 insertions(+), 214 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 428c74c85960..61098a28d6e1 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -132,6 +132,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Implicit()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.ImplicitCommon) + case class Given()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.ImplicitCommon | Flags.Contextual) + case class Erased()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Erased) case class Final()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Final) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 6f63373f064c..a031bb98a068 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -100,7 +100,7 @@ class Definitions { * ImplicitFunctionN traits follow this template: * * trait ImplicitFunctionN[T0,...,T{N-1}, R] extends Object { - * def apply with ($x0: T0, ..., $x{N_1}: T{N-1}): R + * def apply given ($x0: T0, ..., $x{N_1}: T{N-1}): R * } * * ErasedFunctionN traits follow this template: @@ -112,7 +112,7 @@ class Definitions { * ErasedImplicitFunctionN traits follow this template: * * trait ErasedImplicitFunctionN[T0,...,T{N-1}, R] extends Object { - * def apply with (erased $x0: T0, ..., $x{N_1}: T{N-1}): R + * def apply given (erased $x0: T0, ..., $x{N_1}: T{N-1}): R * } * * ErasedFunctionN and ErasedImplicitFunctionN erase to Function0. diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9cccbaf2f678..d23b65909224 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -364,22 +364,22 @@ object Parsers { /** Convert tree to formal parameter list */ - def convertToParams(tree: Tree, mods: Modifiers): List[ValDef] = tree match { - case Parens(t) => convertToParam(t, mods) :: Nil - case Tuple(ts) => ts map (convertToParam(_, mods)) - case t => convertToParam(t, mods) :: Nil + def convertToParams(tree: Tree): List[ValDef] = tree match { + case Parens(t) => convertToParam(t) :: Nil + case Tuple(ts) => ts map (convertToParam(_)) + case t => convertToParam(t) :: Nil } /** Convert tree to formal parameter */ - def convertToParam(tree: Tree, mods: Modifiers, expected: String = "formal parameter"): ValDef = tree match { + def convertToParam(tree: Tree, expected: String = "formal parameter"): ValDef = tree match { case Ident(name) => - makeParameter(name.asTermName, TypeTree(), mods).withSpan(tree.span) + makeParameter(name.asTermName, TypeTree()).withSpan(tree.span) case Typed(Ident(name), tpt) => - makeParameter(name.asTermName, tpt, mods).withSpan(tree.span) + makeParameter(name.asTermName, tpt).withSpan(tree.span) case _ => syntaxError(s"not a legal $expected", tree.span) - makeParameter(nme.ERROR, tree, mods) + makeParameter(nme.ERROR, tree) } /** Convert (qual)ident to type identifier @@ -770,7 +770,7 @@ object Parsers { */ def toplevelTyp(): Tree = checkWildcard(typ()) - /** Type ::= [‘erased’] FunArgTypes (‘=>’ | ‘|=>’) Type + /** Type ::= FunTypeMods FunArgTypes `=>' Type * | HkTypeParamClause `->' Type * | InfixType * FunArgTypes ::= InfixType @@ -779,20 +779,11 @@ object Parsers { */ def typ(): Tree = { val start = in.offset - val imods = modifiers(BitSet(ERASED)) + val imods = modifiers(funTypeMods) def functionRest(params: List[Tree]): Tree = - atSpan(start, in.offset) { - val pmods = - if (in.token == CARROW) { - in.nextToken() - imods | (Contextual | Implicit) - } - else { - accept(ARROW) - imods - } + atSpan(start, accept(ARROW)) { val t = typ() - if (pmods.flags.is(Implicit | Contextual | Erased)) new FunctionWithMods(params, t, pmods) + if (imods.is(Implicit | Contextual | Erased)) new FunctionWithMods(params, t, imods) else Function(params, t) } def funArgTypesRest(first: Tree, following: () => Tree) = { @@ -826,7 +817,7 @@ object Parsers { } openParens.change(LPAREN, -1) accept(RPAREN) - if (imods.is(Implicit) || isValParamList || in.token == ARROW || in.token == CARROW) + if (imods.is(Implicit) || isValParamList || in.token == ARROW) functionRest(ts) else { val ts1 = @@ -858,7 +849,7 @@ object Parsers { else infixType() in.token match { - case ARROW | CARROW => functionRest(t :: Nil) + case ARROW => functionRest(t :: Nil) case MATCH => matchType(EmptyTree, t) case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t case _ => @@ -1134,15 +1125,14 @@ object Parsers { } } - /** Expr ::= [FunArgMods] FunParams =>' Expr - * | [‘erased’] FunParams ‘|=>’ Expr + /** Expr ::= [ClosureMods] FunParams =>' Expr * | Expr1 * FunParams ::= Bindings * | id * | `_' * ExprInParens ::= PostfixExpr `:' Type * | Expr - * BlockResult ::= [FunArgMods] FunParams =>' Block + * BlockResult ::= [ClosureMods] FunParams =>' Block * | Expr1 * Expr1 ::= [‘inline’] `if' `(' Expr `)' {nl} Expr [[semi] else Expr] * | [‘inline’] `if' Expr `then' Expr [[semi] else Expr] @@ -1171,8 +1161,8 @@ object Parsers { def expr(location: Location.Value): Tree = { val start = in.offset - if (in.token == IMPLICIT || in.token == ERASED) { - val imods = modifiers(funArgMods) + if (in.token == IMPLICIT || in.token == ERASED || in.token == GIVEN) { + val imods = modifiers(closureMods) if (in.token == MATCH) implicitMatch(start, imods) else implicitClosure(start, location, imods) } else { @@ -1185,11 +1175,9 @@ object Parsers { finally placeholderParams = saved val t = expr1(location) - if (in.token == ARROW || in.token == CARROW) { + if (in.token == ARROW) { placeholderParams = Nil // don't interpret `_' to the left of `=>` as placeholder - val impliedMods = - if (in.token == CARROW) Modifiers(Implicit | Contextual) else EmptyModifiers - wrapPlaceholders(closureRest(start, location, convertToParams(t, impliedMods))) + wrapPlaceholders(closureRest(start, location, convertToParams(t))) } else if (isWildcard(t)) { placeholderParams = placeholderParams ::: saved @@ -1407,8 +1395,7 @@ object Parsers { } else ident() - /** Expr ::= FunArgMods FunParams `=>' Expr - * | [‘erased’] FunParams ‘|=>’ Expr + /** Expr ::= ClosureMods FunParams `=>' Expr * BlockResult ::= implicit id [`:' InfixType] `=>' Block // Scala2 only */ def implicitClosure(start: Int, location: Location.Value, implicitMods: Modifiers): Tree = @@ -1416,19 +1403,8 @@ object Parsers { def closureRest(start: Int, location: Location.Value, params: List[Tree]): Tree = atSpan(start, in.offset) { - val params1 = - if (in.token == CARROW) { - in.nextToken() - params.map { - case param: ValDef => param.withMods(param.mods | (Implicit | Contextual)) - case param => param - } - } - else { - accept(ARROW) - params - } - Function(params1, if (location == Location.InBlock) block() else expr()) + accept(ARROW) + Function(params, if (location == Location.InBlock) block() else expr()) } /** PostfixExpr ::= InfixExpr [id [nl]] @@ -1853,6 +1829,7 @@ object Parsers { case ABSTRACT => Mod.Abstract() case FINAL => Mod.Final() case IMPLICIT => Mod.Implicit() + case GIVEN => Mod.Given() case ERASED => Mod.Erased() case LAZY => Mod.Lazy() case OVERRIDE => Mod.Override() @@ -1949,9 +1926,13 @@ object Parsers { normalize(loop(start)) } - /** FunArgMods ::= { `implicit` | `erased` } + /** FunArgMods ::= { `implicit` | `erased` } + * ClosureMods ::= { ‘implicit’ | ‘erased’ | ‘given’} + * FunTypeMods ::= { ‘erased’ | ‘given’} */ - def funArgMods: BitSet = BitSet(IMPLICIT, ERASED) + val funArgMods: BitSet = BitSet(IMPLICIT, ERASED) + val closureMods: BitSet = BitSet(GIVEN, IMPLICIT, ERASED) + val funTypeMods: BitSet = BitSet(GIVEN, ERASED) /** Wrap annotation or constructor in New(...). */ def wrapNew(tpt: Tree): Select = Select(New(tpt), nme.CONSTRUCTOR) @@ -2748,7 +2729,7 @@ object Parsers { case Typed(tree @ This(EmptyTypeIdent), tpt) => self = makeSelfDef(nme.WILDCARD, tpt).withSpan(first.span) case _ => - val ValDef(name, tpt, _) = convertToParam(first, EmptyModifiers, "self type clause") + val ValDef(name, tpt, _) = convertToParam(first, "self type clause") if (name != nme.ERROR) self = makeSelfDef(name, tpt).withSpan(first.span) } @@ -2837,10 +2818,10 @@ object Parsers { stats ++= importClause() else if (isExprIntro) stats += expr(Location.InBlock) - else if (isDefIntro(localModifierTokens)) - if (in.token == IMPLICIT || in.token == ERASED) { + else if (isDefIntro(localModifierTokens) || in.token == GIVEN) // !!!! + if (in.token == IMPLICIT || in.token == ERASED || in.token == GIVEN) { val start = in.offset - var imods = modifiers(funArgMods) + var imods = modifiers(closureMods) if (isBindingIntro) stats += implicitClosure(start, Location.InBlock, imods) else if (in.token == MATCH) diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index ab2e28dbe25d..a11dbec4bea8 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -112,8 +112,7 @@ abstract class TokensCommon { //final val SUPERTYPE = 81; enter(SUPERTYPE, ">:") //final val HASH = 82; enter(HASH, "#") final val AT = 83; enter(AT, "@") - //final val CARROW = 84; - //final val VIEWBOUND = 85; enter(VIEWBOUND, "<%") // TODO: deprecate + //final val VIEWBOUND = 84; enter(VIEWBOUND, "<%") // TODO: deprecate val keywords: TokenSet @@ -193,11 +192,10 @@ object Tokens extends TokensCommon { final val SUBTYPE = 80; enter(SUBTYPE, "<:") final val SUPERTYPE = 81; enter(SUPERTYPE, ">:") final val HASH = 82; enter(HASH, "#") - final val CARROW = 84; enter(CARROW, "|=>") - final val VIEWBOUND = 85; enter(VIEWBOUND, "<%") // TODO: deprecate - final val QPAREN = 86; enter(QPAREN, "'(") - final val QBRACE = 87; enter(QBRACE, "'{") - final val QBRACKET = 88; enter(QBRACKET, "'[") + final val VIEWBOUND = 84; enter(VIEWBOUND, "<%") // TODO: deprecate + final val QPAREN = 85; enter(QPAREN, "'(") + final val QBRACE = 86; enter(QBRACE, "'{") + final val QBRACKET = 87; enter(QBRACKET, "'[") /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index c5d338cd1553..8d977fea1d1c 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -125,13 +125,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else simpleNameString(tsym) } - protected def arrowText(contextual: Boolean): Text = if (contextual) " |=> " else " => " - override def toText(tp: Type): Text = controlled { def toTextTuple(args: List[Type]): Text = "(" ~ argsText(args) ~ ")" - def toTextFunction(args: List[Type], contextual: Boolean, isErased: Boolean): Text = + def toTextFunction(args: List[Type], isContextual: Boolean, isErased: Boolean): Text = changePrec(GlobalPrec) { val argStr: Text = if (args.length == 2 && !defn.isTupleType(args.head)) @@ -139,11 +137,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else toTextTuple(args.init) (keywordText("erased ") provided isErased) ~ - argStr ~ arrowText(contextual) ~ argText(args.last) + (keywordText("given ") provided isContextual) ~ + argStr ~ " => " ~ argText(args.last) } - def toTextDependentFunction(appType: MethodType): Text = - "(" ~ paramsText(appType) ~ ")" ~ arrowText(appType.isContextual) ~ toText(appType.resultType) + def toTextDependentFunction(appType: MethodType): Text = // !!!! + (keywordText("given ") provided appType.isImplicitMethod) ~ + "(" ~ paramsText(appType) ~ ") => " ~ toText(appType.resultType) def isInfixType(tp: Type): Boolean = tp match { case AppliedType(tycon, args) => @@ -530,7 +530,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case (arg @ ValDef(_, tpt, _)) :: Nil if tpt.isEmpty => argToText(arg) case _ => "(" ~ Text(args map argToText, ", ") ~ ")" } - changePrec(GlobalPrec) { argsText ~ arrowText(contextual) ~ toText(body) } + changePrec(GlobalPrec) { + (keywordText("given ") provided contextual) ~ + argsText ~ " => " ~ toText(body) + } case InfixOp(l, op, r) => val opPrec = parsing.precedence(op.name) changePrec(opPrec) { toText(l) ~ " " ~ toText(op) ~ " " ~ toText(r) } diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index 54c3a8ada151..70e89d5d7ab7 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -178,10 +178,10 @@ object EtaExpansion extends LiftImpure { * { val xs = es; (x1: T1, ..., xn: Tn) => expr(x1, ..., xn) _ } * * where `T1, ..., Tn` are the paremeter types of the expanded method. - * If `expr` has a contectual function type, the arguments are passed with `with`. + * If `expr` has implicit function type, the arguments are passed with `given`. * E.g. for (1): * - * { val xs = es; (x1, ..., xn) => expr with (x1, ..., xn) } + * { val xs = es; (x1, ..., xn) => expr given (x1, ..., xn) } * * Case (3) applies if the method is curried, i.e. its result type is again a method * type. Case (2) applies if the expected arity of the function type `xarity` differs diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c792f939aaf4..118518cf8faf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2536,8 +2536,9 @@ class Typer extends Namer ctx.warning(ex"${tree.symbol} is eta-expanded even though $pt does not have the @FunctionalInterface annotation.", tree.sourcePos) case _ => } - simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) - } else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol)) + simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) + } + else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol)) readaptSimplified(tpd.Apply(tree, Nil)) else if (wtp.isImplicitMethod) err.typeMismatch(tree, pt) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 16ac1b469a2a..07bc05822dd8 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -137,11 +137,10 @@ ClassQualifier ::= ‘[’ id ‘]’ ### Types ```ebnf -Type ::= [‘erased’] FunArgTypes (‘=>’ | ‘|=>’) Type Function(ts, t) +Type ::= { ‘erased’ | ‘given’} FunArgTypes ‘=>’ Type Function(ts, t) | HkTypeParamClause ‘=>’ Type TypeLambda(ps, t) | MatchType | InfixType -FunArgMods ::= { ‘implicit’ | ‘erased’ } FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ | ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ @@ -176,10 +175,9 @@ TypeParamBounds ::= TypeBounds {‘<%’ Type} {‘:’ Type} ### Expressions ```ebnf -Expr ::= [FunArgMods] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) - | [‘erased’] FunParams ‘|=>’ Expr +Expr ::= [ClosureMods] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) | Expr1 -BlockResult ::= [FunArgMods] FunParams ‘=>’ Block +BlockResult ::= [ClosureMods] FunParams ‘=>’ Block | Expr1 FunParams ::= Bindings | id @@ -304,6 +302,8 @@ InstParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ContextTypes ::= RefinedType {‘,’ RefinedType} +FunArgMods ::= { ‘implicit’ | ‘erased’ } +ClosureMods ::= { ‘implicit’ | ‘erased’ | ‘given’} ``` ### Bindings and Imports diff --git a/docs/docs/reference/instances/implicit-function-types-spec.md b/docs/docs/reference/instances/implicit-function-types-spec.md index 80f50964e586..f70a128baa73 100644 --- a/docs/docs/reference/instances/implicit-function-types-spec.md +++ b/docs/docs/reference/instances/implicit-function-types-spec.md @@ -8,12 +8,12 @@ Initial implementation in (#1775)[https://github.com/lampepfl/dotty/pull/1775]. ## Syntax Type ::= ... - | FunArgTypes `|=>' Type + | `given' FunArgTypes `=>' Type Expr ::= ... - | FunParams `|=>' Expr + | `given' FunParams `=>' Expr Implicit function types associate to the right, e.g. -`S |=> T => U` is the same as `S => (T => U)`. +`given S => given T => U` is the same as `given S => (given T => U)`. ## Implementation @@ -31,7 +31,7 @@ trait ImplicitFunctionN[-T1 , ... , -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. -Anonymous implicit function values `(x1: T1, ..., xn: Tn) |=> e` map +Anonymous implicit function values `given (x1: T1, ..., xn: Tn) => e` map implicit parameters `xi` of types `Ti` to a result given by expression `e`. The scope of each implicit parameter `xi` is `e`. Implicit parameters must have pairwise distinct names. @@ -53,8 +53,8 @@ expression: def apply with (x1: T1, ..., xn: Tn): T = e } -In the case of a single untyped implicit parameter, `(x) |=> e` can be -abbreviated to `x |=> e`. +In the case of a single untyped implicit parameter, `given (x) => e` can be +abbreviated to `given x => e`. A implicit parameter may also be a wildcard represented by an underscore `_`. In that case, a fresh name for the parameter is chosen arbitrarily. @@ -64,7 +64,7 @@ Note: The closing paragraph of the [Anonymous Functions section](https://www functions) of the Scala 2.12 is subsumed by implicit function types and should be removed. -Anonymous implicit functions `(x1: T1, ..., xn: Tn) |=> e` are +Anonymous implicit functions `given (x1: T1, ..., xn: Tn) => e` are automatically inserted around any expression `e` whose expected type is `scala.ImplicitFunctionN[T1, ..., Tn, R]`, unless `e` is itself a function literal. This is analogous to the automatic diff --git a/docs/docs/reference/instances/implicit-function-types.md b/docs/docs/reference/instances/implicit-function-types.md index 676dfaebbead..ab2373cbae1e 100644 --- a/docs/docs/reference/instances/implicit-function-types.md +++ b/docs/docs/reference/instances/implicit-function-types.md @@ -5,7 +5,7 @@ title: "Implicit Function Types and Closures" An implicit function type describes functions with implicit (context) parameters. Example: ```scala -type Contextual[T] = Context |=> T +type Contextual[T] = given Context => T ``` A value of implicit function type is applied to context arguments, in the same way a method with context parameters is applied. For instance: @@ -18,14 +18,14 @@ the same way a method with context parameters is applied. For instance: f(2) // argument left implicit ``` Conversely, if the expected type of an expression `E` is an implicit -function type `(T_1, ..., T_n) |=> U` and `E` is not already an +function type `given (T_1, ..., T_n) => U` and `E` is not already an implicit function value, `E` is converted to an implicit function value by rewriting to ```scala - (x_1: T1, ..., x_n: Tn) |=> E + given (x_1: T1, ..., x_n: Tn) => E ``` where the names `x_1`, ..., `x_n` are arbitrary. Implicit closures are written -with a `|=>` connective instead of `=>` for normal closures. They differ from normal closures in two ways: +with a `given` prefix. They differ from normal closures in two ways: 1. Their parameters are implicit context parameters 2. Their types are implicit function types. @@ -34,11 +34,11 @@ For example, continuing with the previous definitions, ```scala def g(arg: Contextual[Int]) = ... - g(22) // is expanded to g(ctx |=> 22) + g(22) // is expanded to g(given ctx => 22) - g(f(2)) // is expanded to g(ctx |=> f(2) given ctx) + g(f(2)) // is expanded to g(given ctx => f(2) given ctx) - g(ctx |=> f(22) given ctx) // is left as it is + g(given ctx => f(22) given ctx) // is left as it is ``` Implicit function types have considerable expressive power. For instance, here is how they can support the "builder pattern", where @@ -76,13 +76,13 @@ Then, the `table`, `row` and `cell` constructor methods can be defined in terms of implicit function types to avoid the plumbing boilerplate that would otherwise be necessary. ```scala - def table(init: Table |=> Unit) = { + def table(init: given Table => Unit) = { instance t of Table init t } - def row(init: Row |=> Unit) given (t: Table) = { + def row(init: given Row => Unit) given (t: Table) = { instance r of Row init t.add(r) @@ -93,12 +93,12 @@ that would otherwise be necessary. ``` With that setup, the table construction code above compiles and expands to: ```scala - table { $t: Table |=> - row { $r: Row |=> + table { given $t: Table => + row { given $r: Row => cell("top left") given $r cell("top right") given $r } given $t - row { $r: Row |=> + row { given $r: Row => cell("bottom left") given $r cell("bottom right") given $r } given $t diff --git a/docs/docs/reference/instances/replacing-implicits.md b/docs/docs/reference/instances/replacing-implicits.md index b40738ca1b3c..8f80b77a17a8 100644 --- a/docs/docs/reference/instances/replacing-implicits.md +++ b/docs/docs/reference/instances/replacing-implicits.md @@ -41,8 +41,8 @@ Most use cases of implicit classes are already covered by extension methods. For ## Drop: Implicit As A Modifier - Old-style implicit parameters are replaced by `given` parameters. - - Implicit function types `implicit T => U` are written `T |=> U` - - Implicit closures `implicit x => e` are written `x |=> e` + - Implicit function types `implicit T => U` are written `given T => U` + - Implicit closures `implicit x => e` are written `given x => e` - All remaining implicit `val` and `def` definition are replaced by normal `val` or `def` definitions and implicit aliases.= diff --git a/language-server/test/dotty/tools/languageserver/util/PositionContext.scala b/language-server/test/dotty/tools/languageserver/util/PositionContext.scala index 1697e68eb2d1..b892cd978181 100644 --- a/language-server/test/dotty/tools/languageserver/util/PositionContext.scala +++ b/language-server/test/dotty/tools/languageserver/util/PositionContext.scala @@ -24,5 +24,5 @@ class PositionContext(positionMap: Map[CodeMarker, (TestFile, Int, Int)]) { } object PositionContext { - type PosCtx[T] = PositionContext |=> T + type PosCtx[T] = given PositionContext => T } diff --git a/language-server/test/dotty/tools/languageserver/util/actions/Action.scala b/language-server/test/dotty/tools/languageserver/util/actions/Action.scala index 209960902d91..2549a15c40a4 100644 --- a/language-server/test/dotty/tools/languageserver/util/actions/Action.scala +++ b/language-server/test/dotty/tools/languageserver/util/actions/Action.scala @@ -11,7 +11,7 @@ import PositionContext._ * definition, etc.) */ trait Action { - type Exec[T] = (TestServer, TestClient, PositionContext) |=> T + type Exec[T] = given (TestServer, TestClient, PositionContext) => T /** Execute the action. */ def execute(): Exec[Unit] diff --git a/tests/neg/i2006.scala b/tests/neg/i2006.scala index 7142da89619a..11740777ab9c 100644 --- a/tests/neg/i2006.scala +++ b/tests/neg/i2006.scala @@ -4,7 +4,7 @@ object Test { inline def bar(f: ImplicitFunction1[Int, Int]) = f // error def main(args: Array[String]) = { - foo(thisTransaction |=> 43) - bar(thisTransaction |=> 44) + foo(given thisTransaction => 43) + bar(given thisTransaction => 44) } } diff --git a/tests/neg/i2146.scala b/tests/neg/i2146.scala index 32be8734efde..05086d78dc58 100644 --- a/tests/neg/i2146.scala +++ b/tests/neg/i2146.scala @@ -2,7 +2,7 @@ object Test { case class A() case class B() - def foo[A, B]: A |=> B |=> Int = { b: B |=> - 42 // error: found Int, required: A |=> B |=> Int + def foo[A, B]: given A => given B => Int = { given b: B => + 42 // error: found Int, required: given A => given B => Int } } diff --git a/tests/neg/i2514a.scala b/tests/neg/i2514a.scala index 0528fcd20ef1..96de78aabc01 100644 --- a/tests/neg/i2514a.scala +++ b/tests/neg/i2514a.scala @@ -1,10 +1,10 @@ object Foo { def foo(): Int = { - val f: Int |=> Int = (x: Int) |=> 2 * x + val f: given Int => Int = given (x: Int) => 2 * x f given 2 } val f = implicit (x: Int) => x - ((x: Int) |=> x): (Int |=> Int) // error: no implicit argument found + (given (x: Int) => x): (given Int => Int) // error: no implicit argument found } diff --git a/tests/neg/i2642.scala b/tests/neg/i2642.scala index bb52d06c0c3e..8a441dc0276c 100644 --- a/tests/neg/i2642.scala +++ b/tests/neg/i2642.scala @@ -1,10 +1,10 @@ object Foo { - type X = () |=> Int // now ok, used to be: implicit function needs parameters + type X = given () => Int // now ok, used to be: implicit function needs parameters def ff: X = () // error: found: Unit, expected: Int type Y = erased () => Int // error: empty function may not be erased def gg: Y = () // error: found: Unit, expected: Y - type Z = erased () |=> Int // error: empty function may not be erased + type Z = erased given () => Int // error: empty function may not be erased def hh: Z = () // error: found: Unit, expected: Int } diff --git a/tests/neg/i2960.scala b/tests/neg/i2960.scala index 04a77946e61c..f7c1dbe4f6f2 100644 --- a/tests/neg/i2960.scala +++ b/tests/neg/i2960.scala @@ -22,7 +22,7 @@ class Tag(val name: String, this } - def apply[U](f: Tag |=> U)(implicit t: Tag = null): this.type = { + def apply[U](f: given Tag => U)(implicit t: Tag = null): this.type = { if(t != null) t.children += this f given this this diff --git a/tests/neg/i4196.scala b/tests/neg/i4196.scala index 1eea1e565d15..0930d3102903 100644 --- a/tests/neg/i4196.scala +++ b/tests/neg/i4196.scala @@ -1,6 +1,6 @@ object Test { @annotation.tailrec - def foo(i: Unit |=> Int): Unit |=> Int = + def foo(i: given Unit => Int): given Unit => Int = if (i == 0) 0 else diff --git a/tests/neg/i4611a.scala b/tests/neg/i4611a.scala index 31a210ad63fa..7527a0cb113b 100644 --- a/tests/neg/i4611a.scala +++ b/tests/neg/i4611a.scala @@ -1,6 +1,6 @@ // Don't qualify as SAM type because result type is an implicit function type trait Foo { - def foo(x: Int): Int |=> Int + def foo(x: Int): given Int => Int } trait Bar[T] { @@ -12,10 +12,10 @@ class Test { def foo(x: Int) = 1 } - val good2 = new Bar[Int |=> Int] { + val good2 = new Bar[given Int => Int] { def bar(x: Int) = 1 } val bad1: Foo = (x: Int) => 1 // error - val bad2: Bar[implicit Int => Int] = (x: Int) => 1 // error + val bad2: Bar[given Int => Int] = (x: Int) => 1 // error } diff --git a/tests/neg/i4611b.scala b/tests/neg/i4611b.scala index dc53e0fd74cb..d25710a466d1 100644 --- a/tests/neg/i4611b.scala +++ b/tests/neg/i4611b.scala @@ -3,7 +3,7 @@ import scala.concurrent.Future class Response class Request object Request { - type To[T] = Request |=> T + type To[T] = given Request => T } // Don't qualify as SAM type because result type is an implicit function type diff --git a/tests/neg/implicit-shadowing.scala b/tests/neg/implicit-shadowing.scala index f263b3f81761..0ecba452841f 100644 --- a/tests/neg/implicit-shadowing.scala +++ b/tests/neg/implicit-shadowing.scala @@ -20,8 +20,8 @@ object Test { } } - def h[T]: C1[T] |=> Unit = { - def g[U]: C2[U] |=> Unit = { + def h[T]: given C1[T] => Unit = { + def g[U]: given C2[U] => Unit = { implicitly[C1[T]] // OK: no shadowing for evidence parameters implicitly[C2[U]] } diff --git a/tests/pos/Orderings.scala b/tests/pos/Orderings.scala index ec5aa60a745b..64dd7fa7eded 100644 --- a/tests/pos/Orderings.scala +++ b/tests/pos/Orderings.scala @@ -15,6 +15,6 @@ object Orderings { else ev.less(xs.head, ys.head) } - def isLess[T]: T => T => Ord[T] |=> Boolean = + def isLess[T]: T => T => given Ord[T] => Boolean = x => y => implicitly[Ord[T]].less(x, y) } diff --git a/tests/pos/case-getters.scala b/tests/pos/case-getters.scala index 401972808507..aac0f194bc99 100644 --- a/tests/pos/case-getters.scala +++ b/tests/pos/case-getters.scala @@ -1,6 +1,6 @@ -case class Foo(x: 1, y: Int |=> Int) +case class Foo(x: 1, y: given Int => Int) object Test { - val f = Foo(1, (i: Int) |=> i) + val f = Foo(1, given (i: Int) => i) val fx1: 1 = f.x val fx2: 1 = f._1 val fy1: Int = f.y given 1 diff --git a/tests/pos/depfuntype.scala b/tests/pos/depfuntype.scala index 356d3e89b65d..eef9f86ac5b3 100644 --- a/tests/pos/depfuntype.scala +++ b/tests/pos/depfuntype.scala @@ -19,7 +19,7 @@ object Test { // Reproduced here because the one from DottyPredef is lacking a parameter dependency of the return type `ev.type` inline final def implicitly[T](implicit ev: T): ev.type = ev - type IDF = (x: C) |=> x.M + type IDF = given (x: C) => x.M implicit val ic: C = ??? diff --git a/tests/pos/eff-compose.scala b/tests/pos/eff-compose.scala index b719cedf2431..9165ae215d77 100644 --- a/tests/pos/eff-compose.scala +++ b/tests/pos/eff-compose.scala @@ -5,7 +5,7 @@ object Test { // Type X => Y abstract class Fun[-X, Y] { type Eff <: Effect - def apply(x: X): Eff |=> Y + def apply(x: X): given Eff => Y } // Type X -> Y @@ -13,7 +13,7 @@ object Test { // def map(f: A => B)(xs: List[A]): List[B] def map[A, B, E <: Effect](f: Fun[A, B] { type Eff = E})(xs: List[A]) - : E |=> List[B] = + : given E => List[B] = xs.map(f.apply) // def mapFn[A, B]: (A => B) -> List[A] -> List[B] @@ -31,7 +31,7 @@ object Test { def apply(f: Fun[A, B] { type Eff = E}) = new Fun[List[A], List[B]] { type Eff = E - def apply(xs: List[A]): Eff |=> List[B] = + def apply(xs: List[A]): given Eff => List[B] = map(f)(xs) } } @@ -43,7 +43,7 @@ object Test { (f: Fun[A, B] { type Eff = E1}) (g: Fun[B, C] { type Eff = E2}) (x: A): - E1 & E2 |=> C = g(f(x)) + given E1 & E2 => C = g(f(x)) // def composeFn: (A => B) -> (B => C) -> A -> C def composeFn[A, B, C, E1 <: Effect, E2 <: Effect]: diff --git a/tests/pos/givenIn.scala b/tests/pos/givenIn.scala index 6b96900fa4f8..c7f446077b8b 100644 --- a/tests/pos/givenIn.scala +++ b/tests/pos/givenIn.scala @@ -2,7 +2,7 @@ object Test { import scala.compiletime.constValue class Context { - inline def givenIn[T](op: => Context |=> T) = { + inline def givenIn[T](op: => given Context => T) = { instance of Context = this op } diff --git a/tests/pos/ho-implicits.scala b/tests/pos/ho-implicits.scala index 4dbb2a2e47a3..957926ee380c 100644 --- a/tests/pos/ho-implicits.scala +++ b/tests/pos/ho-implicits.scala @@ -1,9 +1,9 @@ object Test2 { - implicit def __1: Int |=> String = s"implicit: ${implicitly[Int]}" + implicit def __1: given Int => String = s"implicit: ${implicitly[Int]}" implicit def __2: Int = 42 - def f: String |=> Int = implicitly[String].length + def f: given String => Int = implicitly[String].length f: Int } \ No newline at end of file diff --git a/tests/pos/i2146.scala b/tests/pos/i2146.scala index 2c920b15d5fa..98816b2c2269 100644 --- a/tests/pos/i2146.scala +++ b/tests/pos/i2146.scala @@ -2,12 +2,12 @@ object Test { case class A() case class B() - def simple[A]: A |=> A = implicitly[A] + def simple[A]: given A => A = implicitly[A] - def foo[A, B]: A |=> B |=> (A, B) = + def foo[A, B]: given A => given B => (A, B) = (implicitly[A], implicitly[B]) - def bar[A, B]: A |=> B |=> (A, B) = { a: A |=> + def bar[A, B]: given A => given B => (A, B) = { given (a: A) => (implicitly[A], implicitly[B]) } @@ -18,11 +18,11 @@ object Test { println(foo[A, B]) println(foo[A, B] given a) println(foo given a given b) - val s: A |=> A = simple[A] + val s: given A => A = simple[A] println(s) - val x0: A |=> B |=> (A, B) = foo[A, B] + val x0: given A => given B => (A, B) = foo[A, B] println(x0) - val x1: B |=> (A, B) = foo[A, B] + val x1: given B => (A, B) = foo[A, B] println(x1) println(bar[A, B]) diff --git a/tests/pos/i2278.scala b/tests/pos/i2278.scala index 13b7d9dddc7c..2f3a4d3e3a25 100644 --- a/tests/pos/i2278.scala +++ b/tests/pos/i2278.scala @@ -4,7 +4,7 @@ object Fluent { } trait CC[T] - type Context[Alg[x[_]] <: Foo[x], E] = Alg[CC] |=> CC[E] + type Context[Alg[x[_]] <: Foo[x], E] = given Alg[CC] => CC[E] def meth1[T]() : Context[Foo, T] = { implicitly[Foo[CC]].meth1() diff --git a/tests/pos/i2671.scala b/tests/pos/i2671.scala index 90ec7c91bb9e..e187b98a1ced 100644 --- a/tests/pos/i2671.scala +++ b/tests/pos/i2671.scala @@ -1,10 +1,10 @@ object Foo { - def map[E](f: E |=> Int): (E |=> Int) = ??? + def map[E](f: given E => Int): (given E => Int) = ??? implicit def i: Int = ??? - def f: Int |=> Int = ??? + def f: given Int => Int = ??? val a: Int = map(f) diff --git a/tests/pos/i2749.scala b/tests/pos/i2749.scala index 0897f8a386e0..959ce858a9b1 100644 --- a/tests/pos/i2749.scala +++ b/tests/pos/i2749.scala @@ -1,23 +1,23 @@ object Test { - val f: (Int |=> Char) |=> Boolean = ??? + val f: given (given Int => Char) => Boolean = ??? implicit val n: Int = 3 - implicit val g: Int |=> Char = ??? + implicit val g: given Int => Char = ??? f : Boolean } object Test2 { - val f: (Int |=> Char) |=> Boolean = ??? + val f: given (given Int => Char) => Boolean = ??? implicit val s: String = null - implicit val g: Int |=> String |=> Char = ??? + implicit val g: given Int => given String => Char = ??? f : Boolean } object Test3 { - val f: (Int |=> String |=> Char) |=> Boolean = ??? + val f: given (given Int => given String => Char) => Boolean = ??? implicit val n: Int = 3 - implicit val g: Int |=> Char = ??? + implicit val g: given Int => Char = ??? f : Boolean } diff --git a/tests/pos/i3692.scala b/tests/pos/i3692.scala index 77b68bf27c5b..cf799d4de02b 100644 --- a/tests/pos/i3692.scala +++ b/tests/pos/i3692.scala @@ -6,7 +6,7 @@ object Main { //val b: Int => Int = a def main(args: Array[String]): Unit = { - val choose: (c: C) |=> Set[Int] = Set.empty + val choose: given (c: C) => Set[Int] = Set.empty val b0: (C) => Set[Int] = choose given _ val b1: (c: C) => Set[Int] = choose given _ def applyF(f: (c: C) => Set[Int]) = f(new C{type T=Int}) diff --git a/tests/pos/i4125.scala b/tests/pos/i4125.scala index 61310f14b246..49ca8fd8daf3 100644 --- a/tests/pos/i4125.scala +++ b/tests/pos/i4125.scala @@ -1,4 +1,4 @@ object Test { def foo: (erased (x: Int, y: Int) => Int) = erased (x, y) => 1 - def bar: (erased (x: Int, y: Int) |=> Int) = erased (x, y) |=> 1 + def bar: (erased given (x: Int, y: Int) => Int) = erased given (x, y) => 1 } diff --git a/tests/pos/i4196.scala b/tests/pos/i4196.scala index e880f01cd7a4..669ac0bc935c 100644 --- a/tests/pos/i4196.scala +++ b/tests/pos/i4196.scala @@ -1,6 +1,6 @@ object Test { @annotation.tailrec - def foo(i: Unit |=> Int): Unit |=> Int = + def foo(i: given Unit => Int): given Unit => Int = if (i == 0) 0 else diff --git a/tests/pos/i4203.scala b/tests/pos/i4203.scala index 3c75e7dee3df..4577d1709dbe 100644 --- a/tests/pos/i4203.scala +++ b/tests/pos/i4203.scala @@ -1,7 +1,7 @@ case class Box[Z](unbox: Z) object Test { - def foo(b: Box[Int |=> Int]): Int = b match { + def foo(b: Box[given Int => Int]): Int = b match { case Box(f) => implicit val i: Int = 1 f diff --git a/tests/pos/i4725.scala b/tests/pos/i4725.scala index 72fca9940310..1ccd34d3a328 100644 --- a/tests/pos/i4725.scala +++ b/tests/pos/i4725.scala @@ -1,7 +1,7 @@ object Test1 { trait T[A] - def foo[S[_], A] given (ev: T[A] |=> T[S[A]]): Unit = () + def foo[S[_], A] given (ev: given T[A] => T[S[A]]): Unit = () implicit def bar[A] given (ev: T[A]): T[List[A]] = ??? foo[List, Int] @@ -11,7 +11,7 @@ object Test2 { trait T trait S - def foo given (ev: T |=> S): Unit = () + def foo given (ev: given T => S): Unit = () implicit def bar given (ev: T): S = ??? foo diff --git a/tests/pos/i4753.scala b/tests/pos/i4753.scala index bc83d980a6b6..dc5999506a8a 100644 --- a/tests/pos/i4753.scala +++ b/tests/pos/i4753.scala @@ -1,13 +1,13 @@ class A trait Foo { - def foo: A |=> Int + def foo: given A => Int } class Test { - println(new FooI{}) + new FooI{} } class FooI extends Foo { - def foo: A |=> Int = 3 + def foo: given A => Int = 3 } \ No newline at end of file diff --git a/tests/pos/i4753b.scala b/tests/pos/i4753b.scala index 606986f2ddbc..a6a2ce0103d9 100644 --- a/tests/pos/i4753b.scala +++ b/tests/pos/i4753b.scala @@ -1,7 +1,7 @@ class Foo1 { - def foo: String |=> Int = 1 + def foo: given String => Int = 1 } class Foo2 extends Foo1 { - override def foo: String |=> Int = 2 + override def foo: given String => Int = 2 } diff --git a/tests/pos/implicit-dep.scala b/tests/pos/implicit-dep.scala index f6fe80cc43c1..620b95e96a7d 100644 --- a/tests/pos/implicit-dep.scala +++ b/tests/pos/implicit-dep.scala @@ -5,5 +5,5 @@ trait HasT { object Test { - def foo: Int |=> (g: HasT) |=> g.T = ??? + def foo: given Int => given (g: HasT) => g.T = ??? } diff --git a/tests/pos/implicitFuns.scala b/tests/pos/implicitFuns.scala index 555acf7310d0..7eb2ca40f58b 100644 --- a/tests/pos/implicitFuns.scala +++ b/tests/pos/implicitFuns.scala @@ -10,7 +10,7 @@ class ConfManagement(papers: List[Paper], realScore: Map[Paper, Int]) extends Ap private def hasConflict(ps1: Set[Person], ps2: Iterable[Person]) = ps2.exists(ps1 contains _) - type Viewable[T] = Viewers |=> T + type Viewable[T] = given Viewers => T def vs: Viewable[Viewers] = implicitly @@ -52,7 +52,7 @@ object Orderings extends App { x => y => x < y } - implicit def __2[T]: Ord[T] |=> Ord[List[T]] = new Ord[List[T]] { + implicit def __2[T]: given Ord[T] => Ord[List[T]] = new Ord[List[T]] { def less: List[T] => List[T] => Boolean = xs => ys => if ys.isEmpty then false @@ -61,7 +61,7 @@ object Orderings extends App { else isLess(xs.head)(ys.head) } - def isLess[T]: T => T => Ord[T] |=> Boolean = + def isLess[T]: T => T => given Ord[T] => Boolean = x => y => implicitly[Ord[T]].less(x)(y) println(isLess(Nil)(List(1, 2, 3))) diff --git a/tests/pos/inline-apply.scala b/tests/pos/inline-apply.scala index 1e58b6bceb41..358b89abcd92 100644 --- a/tests/pos/inline-apply.scala +++ b/tests/pos/inline-apply.scala @@ -3,9 +3,9 @@ class Context object Test { def transform()(implicit ctx: Context) = { - inline def withLocalOwner[T](op: Context |=> T) = op given ctx + inline def withLocalOwner[T](op: given Context => T) = op given ctx - withLocalOwner { ctx |=> () } + withLocalOwner { given ctx => } } } diff --git a/tests/pos/reference/instances.scala b/tests/pos/reference/instances.scala index 6606f9518a43..a4e05e1b3564 100644 --- a/tests/pos/reference/instances.scala +++ b/tests/pos/reference/instances.scala @@ -92,12 +92,12 @@ object Instances extends Common { } case class Context(value: String) - val c0: Context |=> String = ctx |=> ctx.value - val c1: (Context |=> String) = (ctx: Context) |=> ctx.value + val c0: given Context => String = given ctx => ctx.value + val c1: (given Context => String) = given (ctx: Context) => ctx.value class A class B - val ab: (x: A, y: B) |=> Int = (a: A, b: B) |=> 22 + val ab: given (x: A, y: B) => Int = given (a: A, b: B) => 22 trait TastyAPI { type Symbol @@ -158,7 +158,7 @@ object PostConditions { def result[T] given (wrapped: WrappedResult[T]): T = wrapped.unwrap instance { - def (x: T) ensuring[T] (condition: WrappedResult[T] |=> Boolean): T = { + def (x: T) ensuring[T] (condition: given WrappedResult[T] => Boolean): T = { assert(condition given WrappedResult(x)) x } diff --git a/tests/run/builder.scala b/tests/run/builder.scala index 436b14ed421f..b9db863ea3bb 100644 --- a/tests/run/builder.scala +++ b/tests/run/builder.scala @@ -16,13 +16,13 @@ case class Cell(elem: String) object Test { - def table(init: Table |=> Unit) = { + def table(init: given Table => Unit) = { implicit val t = new Table init t } - def row(init: Row |=> Unit)(implicit t: Table) = { + def row(init: given Row => Unit)(implicit t: Table) = { implicit val r = new Row init t.add(r) diff --git a/tests/run/config.scala b/tests/run/config.scala index 7710e0d5a2af..c60731587ca3 100644 --- a/tests/run/config.scala +++ b/tests/run/config.scala @@ -35,7 +35,7 @@ object Imperative { } object Configs { - type Configured[T] = Config |=> T + type Configured[T] = given Config => T def config: Configured[Config] = implicitly[Config] } @@ -47,7 +47,7 @@ object Exceptions { private[Exceptions] def throwE() = throw new E } - type Possibly[T] = CanThrow |=> T + type Possibly[T] = given CanThrow => T def require(p: Boolean)(implicit ct: CanThrow): Unit = if (!p) ct.throwE() diff --git a/tests/run/eff-dependent.scala b/tests/run/eff-dependent.scala index 5ecfe0a08c8a..59be3c89e4b6 100644 --- a/tests/run/eff-dependent.scala +++ b/tests/run/eff-dependent.scala @@ -5,7 +5,7 @@ object Test extends App { // Type X => Y abstract class Fun[-X, +Y] { type Eff <: Effect - def apply(x: X): Eff |=> Y + def apply(x: X): given Eff => Y } class CanThrow extends Effect @@ -18,18 +18,18 @@ object Test extends App { implicit val ci: CanIO = new CanIO // def map(f: A => B)(xs: List[A]): List[B] - def map[A, B](f: Fun[A, B])(xs: List[A]): f.Eff |=> List[B] = + def map[A, B](f: Fun[A, B])(xs: List[A]): given f.Eff => List[B] = xs.map(f.apply) // def mapFn[A, B]: (A => B) -> List[A] -> List[B] - def mapFn[A, B]: (f: Fun[A, B]) => List[A] => f.Eff |=> List[B] = + def mapFn[A, B]: (f: Fun[A, B]) => List[A] => given f.Eff => List[B] = f => xs => map(f)(xs) // def compose(f: A => B)(g: B => C)(x: A): C - def compose[A, B, C](f: Fun[A, B])(g: Fun[B, C])(x: A): f.Eff |=> g.Eff |=> C = g(f(x)) + def compose[A, B, C](f: Fun[A, B])(g: Fun[B, C])(x: A): given f.Eff => given g.Eff => C = g(f(x)) // def composeFn: (A => B) -> (B => C) -> A -> C - def composeFn[A, B, C]: (f: Fun[A, B]) => (g: Fun[B, C]) => A => f.Eff |=> g.Eff |=> C = + def composeFn[A, B, C]: (f: Fun[A, B]) => (g: Fun[B, C]) => A => given f.Eff => given g.Eff => C = f => g => x => compose(f)(g)(x) assert(mapFn(i2s)(List(1, 2, 3)).mkString == "123") diff --git a/tests/run/erased-23.check b/tests/run/erased-23.check index aac6a431ff8a..efbbe4dddf06 100644 --- a/tests/run/erased-23.check +++ b/tests/run/erased-23.check @@ -1 +1,2 @@ lambda1 +lambda2 diff --git a/tests/run/erased-23.scala b/tests/run/erased-23.scala index 17b32d344b78..bb5738c349e8 100644 --- a/tests/run/erased-23.scala +++ b/tests/run/erased-23.scala @@ -1,13 +1,22 @@ object Test { def main(args: Array[String]): Unit = { - fun { erased (x: Int) |=> + fun { given erased (x: Int) => println("lambda1") "abc" } + + fun2 { erased given (x: Int) => + println("lambda2") + "abc" + } + } + + def fun(f: given erased Int => String): String = { + f given 35 } - def fun(f: erased Int |=> String): String = { + def fun2(f: erased given Int => String): String = { f given 35 } } diff --git a/tests/run/i2642.scala b/tests/run/i2642.scala index d0f4bcaf0b00..b3b849f45a2f 100644 --- a/tests/run/i2642.scala +++ b/tests/run/i2642.scala @@ -1,7 +1,7 @@ // Tests nullary implicit function types object Test extends App { class I - type X = () |=> Int + type X = given () => Int def ff: X = 2 assert(ff == 2) } diff --git a/tests/run/i2939.scala b/tests/run/i2939.scala index 9c345067031d..4046a7fd347c 100644 --- a/tests/run/i2939.scala +++ b/tests/run/i2939.scala @@ -7,7 +7,7 @@ class Tag(val name: String, val buffer: Buffer[Tag] = ArrayBuffer()) { s"${" " * n}" } - def apply[U](f: Tag |=> U)(implicit tag: Tag = null): this.type = { + def apply[U](f: given Tag => U)(implicit tag: Tag = null): this.type = { f given this if(tag != null) tag.buffer += this this diff --git a/tests/run/i3448.scala b/tests/run/i3448.scala index e25fabbd2c26..f412ba61f3c8 100644 --- a/tests/run/i3448.scala +++ b/tests/run/i3448.scala @@ -1,11 +1,11 @@ object Test extends App { case class C(x: Int) - type IF[T] = C |=> T + type IF[T] = given C => T val x: IF[Int] = implicitly[C].x - val xs0: List[IF[Int]] = List(_ |=> x) + val xs0: List[IF[Int]] = List(given _ => x) val xs: List[IF[Int]] = List(x) val ys: IF[List[Int]] = xs.map(x => x) val zs = ys given C(22) diff --git a/tests/run/implicit-shortcut-bridge.scala b/tests/run/implicit-shortcut-bridge.scala index 7ad474ea507f..fed9b9772e96 100644 --- a/tests/run/implicit-shortcut-bridge.scala +++ b/tests/run/implicit-shortcut-bridge.scala @@ -1,17 +1,17 @@ abstract class A[T] { def foo: T } -class B extends A[Int |=> Int] { +class B extends A[given Int => Int] { // No bridge needed for foo$direct - def foo: Int |=> Int = 1 + def foo: given Int => Int = 1 } -abstract class X[T] extends A[T |=> T] { - def foo: T |=> T +abstract class X[T] extends A[given T => T] { + def foo: given T => T } class Y extends X[Int] { - def foo: Int |=> Int = 1 + def foo: given Int => Int = 1 } object Test { diff --git a/tests/run/implicitFunctionXXL.scala b/tests/run/implicitFunctionXXL.scala index 7c3093890e00..0ba6c3ffc089 100644 --- a/tests/run/implicitFunctionXXL.scala +++ b/tests/run/implicitFunctionXXL.scala @@ -5,7 +5,7 @@ object Test { implicit val intWorld: Int = 42 implicit val strWorld: String = "Hello " - val i1 = ( (x1: Int, + val i1 = (given (x1: Int, x2: String, x3: Int, x4: Int, @@ -30,7 +30,7 @@ object Test { x23: Int, x24: Int, x25: Int, - x26: Int) |=> x2 + x1) + x26: Int) => x2 + x1) println(i1) } diff --git a/tests/run/implicitFuns.scala b/tests/run/implicitFuns.scala index 0398463d6cc0..508d54c31569 100644 --- a/tests/run/implicitFuns.scala +++ b/tests/run/implicitFuns.scala @@ -3,15 +3,15 @@ object Test { implicit val world: String = "world!" - val i1 = ((s: String) |=> s.length > 2) - val i2 = {(s: String) |=> s.length > 2} + val i1 = (given (s: String) => s.length > 2) + val i2 = {given (s: String) => s.length > 2} assert(i1) assert(i2) - val x: String |=> Boolean = { (s: String) |=> s.length > 2 } + val x: given String => Boolean = { given (s: String) => s.length > 2 } - val xx: (String, Int) |=> Int = (x: String, y: Int) |=> x.length + y + val xx: given (String, Int) => Int = given (x: String, y: Int) => x.length + y val y: String => Boolean = x given _ @@ -22,20 +22,20 @@ object Test { val yy: (String, Int) => Any = xx given (_, _) - val z1: String |=> Boolean = implicitly[String].length >= 2 + val z1: given String => Boolean = implicitly[String].length >= 2 assert(z1) - type StringlyBool = String |=> Boolean + type StringlyBool = given String => Boolean val z2: StringlyBool = implicitly[String].length >= 2 assert(z2) - type Stringly[T] = String |=> T + type Stringly[T] = given String => T val z3: Stringly[Boolean] = implicitly[String].length >= 2 assert(z3) - type GenericImplicit[X] = X |=> Boolean + type GenericImplicit[X] = given X => Boolean val z4: GenericImplicit[String] = implicitly[String].length >= 2 assert(z4) @@ -76,7 +76,7 @@ object Contextual { val Source = new Key[String] val Options = new Key[List[String]] - type Ctx[T] = Context |=> T + type Ctx[T] = given Context => T def ctx: Ctx[Context] = implicitly[Context] @@ -151,7 +151,7 @@ object TransactionalExplicit { } object Transactional { - type Transactional[T] = Transaction |=> T + type Transactional[T] = given Transaction => T def transaction[T](op: Transactional[T]) = { implicit val trans: Transaction = new Transaction @@ -216,7 +216,7 @@ object TransactionalExpansion { } object TransactionalAbstracted { - type Transactional[T] = Transaction |=> T + type Transactional[T] = given Transaction => T trait TransOps { def thisTransaction: Transactional[Transaction] diff --git a/tests/run/implicitFuns2.scala b/tests/run/implicitFuns2.scala index 8f59819015a9..e947066bdc67 100644 --- a/tests/run/implicitFuns2.scala +++ b/tests/run/implicitFuns2.scala @@ -2,27 +2,27 @@ class A class B trait Foo { - def foo: A |=> B |=> Int + def foo: given A => given B => Int } class Foo1 extends Foo { - def foo: A |=> B |=> Int = 1 + def foo: given A => given B => Int = 1 } class Foo2 extends Foo1 { - override def foo: A |=> B |=> Int = 2 + override def foo: given A => given B => Int = 2 } trait Foo3 extends Foo { - override def foo: A |=> B |=> Int = 3 + override def foo: given A => given B => Int = 3 } class Bar[T] { - def bar: A |=> T = null.asInstanceOf[T] + def bar: given A => T = null.asInstanceOf[T] } -class Bar1 extends Bar[B |=> Int] { - override def bar: A |=> B |=> Int = 1 +class Bar1 extends Bar[given B => Int] { + override def bar: given A => given B => Int = 1 } object Test { diff --git a/tests/run/implicitShortcut/Base_1.scala b/tests/run/implicitShortcut/Base_1.scala index bbba570bcf38..91db5ee94880 100644 --- a/tests/run/implicitShortcut/Base_1.scala +++ b/tests/run/implicitShortcut/Base_1.scala @@ -3,6 +3,6 @@ package implicitShortcut class C abstract class Base[T] { - def foo(x: T): C |=> T = x + def foo(x: T): given C => T = x } \ No newline at end of file diff --git a/tests/run/implicitShortcut/Derived_2.scala b/tests/run/implicitShortcut/Derived_2.scala index b66bfcc17c02..045fcbc10e6d 100644 --- a/tests/run/implicitShortcut/Derived_2.scala +++ b/tests/run/implicitShortcut/Derived_2.scala @@ -1,5 +1,5 @@ package implicitShortcut class Derived extends Base[Int] { - override def foo(x: Int): C |=> Int = 42 + override def foo(x: Int): given C => Int = 42 } \ No newline at end of file diff --git a/tests/run/returning.scala b/tests/run/returning.scala index f7ce747267b6..2271bb443016 100644 --- a/tests/run/returning.scala +++ b/tests/run/returning.scala @@ -14,7 +14,7 @@ object NonLocalReturns { def throwReturn[T](result: T)(implicit returner: ReturnThrowable[T]): Nothing = returner.throwReturn(result) - def returning[T](op: ReturnThrowable[T] |=> T): T = { + def returning[T](op: given ReturnThrowable[T] => T): T = { val returner = new ReturnThrowable[T] try op given returner catch { diff --git a/tests/run/tagless.scala b/tests/run/tagless.scala index 41e1e4f87fbd..952aa3e44274 100644 --- a/tests/run/tagless.scala +++ b/tests/run/tagless.scala @@ -107,7 +107,7 @@ object Test extends App { object CanThrow { private class Exc(msg: String) extends Exception(msg) def _throw(msg: String) given CanThrow: Nothing = throw new Exc(msg) - def _try[T](op: CanThrow |=> T)(handler: String => T): T = { + def _try[T](op: Maybe[T])(handler: String => T): T = { instance of CanThrow try op catch { @@ -117,7 +117,7 @@ object Test extends App { } import CanThrow._ - type Maybe[T] = CanThrow |=> T + type Maybe[T] = given CanThrow => T def show[T](op: Maybe[T]): Unit = println(_try(op.toString)(identity)) @@ -244,7 +244,7 @@ object Test extends App { } // Abstracting over multiple typeclasses - type Ring[T] = Exp[T] |=> Mult[T] |=> T + type Ring[T] = given Exp[T] => given Mult[T] => T def tfm1a[T]: Ring[T] = add(lit(7), neg(mul(lit(1), lit(2)))) def tfm2a[T]: Ring[T] = mul(lit(7), tf1) diff --git a/tests/run/tasty-getfile-implicit-fun-context/Macro_1.scala b/tests/run/tasty-getfile-implicit-fun-context/Macro_1.scala index afc84c7e67d3..ae0fef057ab6 100644 --- a/tests/run/tasty-getfile-implicit-fun-context/Macro_1.scala +++ b/tests/run/tasty-getfile-implicit-fun-context/Macro_1.scala @@ -3,7 +3,7 @@ import scala.tasty.Reflection object SourceFiles { - type Macro[X] = Reflection |=> Expr[X] + type Macro[X] = given Reflection => Expr[X] def tastyContext(implicit ctx: Reflection): Reflection = ctx implicit inline def getThisFile: String = diff --git a/tests/run/tasty-implicit-fun-context-2/Macro_1.scala b/tests/run/tasty-implicit-fun-context-2/Macro_1.scala index 170080aab25b..07c838ee6503 100644 --- a/tests/run/tasty-implicit-fun-context-2/Macro_1.scala +++ b/tests/run/tasty-implicit-fun-context-2/Macro_1.scala @@ -3,13 +3,14 @@ import scala.tasty.Reflection object Foo { - type Macro[X] = Reflection |=> Expr[X] - type Tastier[X] = Reflection |=> X + type Macro[X] = given Reflection => Expr[X] + type Tastier[X] = given Reflection => X implicit inline def foo: String = ~fooImpl - def fooImpl(implicit reflect: Reflection): Reflection |=> Tastier[Reflection |=> Macro[String]] = { + def fooImpl(implicit reflect: Reflection): given Reflection => Tastier[given Reflection => Macro[String]] = { '("abc") } + } From f324ea5f039f00bfdd0f50b003274cc8c790aef7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 Jan 2019 15:59:16 +0100 Subject: [PATCH 05/16] Fix new tests after merge # Conflicts: # tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala # tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala --- .../tools/languageserver/util/CodeTester.scala | 4 ++-- .../interpreter/TastyInterpreter.scala | 2 +- .../interpreter/TreeInterpreter.scala | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/language-server/test/dotty/tools/languageserver/util/CodeTester.scala b/language-server/test/dotty/tools/languageserver/util/CodeTester.scala index 20bf80a3d543..44f553832e89 100644 --- a/language-server/test/dotty/tools/languageserver/util/CodeTester.scala +++ b/language-server/test/dotty/tools/languageserver/util/CodeTester.scala @@ -237,7 +237,7 @@ class CodeTester(projects: List[Project]) { private def doAction(action: Action): this.type = { try { - action.execute() with (testServer, testServer.client, positions) + action.execute() given (testServer, testServer.client, positions) } catch { case ex: AssertionError => val sourcesStr = @@ -252,7 +252,7 @@ class CodeTester(projects: List[Project]) { | |$sourcesStr | - |while executing action: ${action.show with positions} + |while executing action: ${action.show given positions} | """.stripMargin val assertionError = new AssertionError(msg + ex.getMessage) diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala index ba0c87ce5191..bb389a21cd0b 100644 --- a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala @@ -14,7 +14,7 @@ class TastyInterpreter extends TastyConsumer { case DefDef("main", _, _, _, Some(rhs)) => val interpreter = new jvm.Interpreter(reflect) - interpreter.eval(rhs) with Map.empty + interpreter.eval(rhs) given Map.empty // TODO: recurse only for PackageDef, ClassDef case tree => super.traverseTree(tree) diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala index 3ce4283ff4f6..bd96c338677a 100644 --- a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala @@ -13,15 +13,15 @@ abstract class TreeInterpreter[R <: Reflection & Singleton](val reflect: R) { /** Representation of objects and values in the interpreter */ type AbstractAny - type Result = Env |=> AbstractAny + type Result = given Env => AbstractAny def localValue(sym: Symbol)(implicit env: Env): LocalValue = env(sym) - def withLocalValue[T](sym: Symbol, value: LocalValue)(in: Env |=> T)(implicit env: Env): T = - in with env.updated(sym, value) + def withLocalValue[T](sym: Symbol, value: LocalValue)(in: given Env => T)(implicit env: Env): T = + in given env.updated(sym, value) - def withLocalValues[T](syms: List[Symbol], values: List[LocalValue])(in: Env |=> T)(implicit env: Env): T = - in with (env ++ syms.zip(values)) + def withLocalValues[T](syms: List[Symbol], values: List[LocalValue])(in: given Env => T)(implicit env: Env): T = + in given (env ++ syms.zip(values)) def interpretCall(inst: AbstractAny, sym: DefSymbol, args: List[AbstractAny]): Result = { // TODO @@ -65,7 +65,7 @@ abstract class TreeInterpreter[R <: Reflection & Singleton](val reflect: R) { def interpretBlock(stats: List[Statement], expr: Term): Result = { val newEnv = stats.foldLeft(implicitly[Env])((accEnv, stat) => stat match { case ValDef(name, tpt, Some(rhs)) => - def evalRhs = eval(rhs) with accEnv + def evalRhs = eval(rhs) given accEnv val evalRef: LocalValue = if (stat.symbol.flags.is(Flags.Lazy)) LocalValue.lazyValFrom(evalRhs) else if (stat.symbol.flags.is(Flags.Mutable)) LocalValue.varFrom(evalRhs) @@ -76,10 +76,10 @@ abstract class TreeInterpreter[R <: Reflection & Singleton](val reflect: R) { // TODO: record the environment for closure purposes accEnv case stat => - eval(stat) with accEnv + eval(stat) given accEnv accEnv }) - eval(expr) with newEnv + eval(expr) given newEnv } def interpretUnit(): AbstractAny From 319d8746362eb6452b2ff6ced247493479b82ac7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 Jan 2019 20:44:08 +0100 Subject: [PATCH 06/16] Fix new tests after changes --- compiler/test-resources/repl/3932 | 4 ++-- .../test/dotty/tools/languageserver/DiagnosticsTest.scala | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/test-resources/repl/3932 b/compiler/test-resources/repl/3932 index 4a3e16000756..2751fa1fee7f 100644 --- a/compiler/test-resources/repl/3932 +++ b/compiler/test-resources/repl/3932 @@ -1,2 +1,2 @@ -scala> def fun[T](x: T): List[T] |=> Int = ??? -def fun[T](x: T): List[T] |=> Int +scala> def fun[T](x: T): given List[T] => Int = ??? +def fun[T](x: T): given List[T] => Int diff --git a/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala b/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala index 4f9b8f47a232..85a3895189e9 100644 --- a/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala +++ b/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala @@ -18,10 +18,10 @@ class DiagnosticsTest { @Test def diagnosticMissingLambdaBody: Unit = code"""object Test { - | Nil.map(x => x).filter(x$m1 =>$m2) - |$m3}""".withSource + | Nil.map(x => x).filter(x$m1 =>$m2)$m3 + |}""".withSource .diagnostics(m1, - (m2 to m3, "expression expected", Error, Some(IllegalStartSimpleExprID)), + (m2 to m2, "expression expected", Error, Some(IllegalStartSimpleExprID)), (m1 to m1, """Found: Null |Required: Boolean""".stripMargin, Error, Some(TypeMismatchID)) ) From 1c90ec295448457ded1c6c0d901c3720ef918556 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 Jan 2019 22:28:37 +0100 Subject: [PATCH 07/16] Syntax change: `inferred for` instead of `instance of` --- .../reference/instances/context-params.md | 8 +- .../instances/implicit-conversions.md | 12 +-- .../instances/implicit-function-types.md | 18 ++-- .../docs/reference/instances/instance-defs.md | 88 ++++++++++--------- tests/pos/reference/instances.scala | 46 +++++----- tests/run/tagless.scala | 20 ++--- 6 files changed, 97 insertions(+), 95 deletions(-) diff --git a/docs/docs/reference/instances/context-params.md b/docs/docs/reference/instances/context-params.md index 471c6081b86f..5a5975bce751 100644 --- a/docs/docs/reference/instances/context-params.md +++ b/docs/docs/reference/instances/context-params.md @@ -1,6 +1,6 @@ --- layout: doc-page -title: "Implicit Parameters and Arguments" +title: "Inferred Parameters and Arguments" --- A new syntax for implicit parameters aligns definition and call syntax. Parameter definitions and method arguments both follow a `given` keyword. On the definition side, the old syntax @@ -54,8 +54,8 @@ lists in a definition. Example: ```scala def f given (u: Universe) (x: u.T) given Context = ... -instance global of Universe { type T = String ... } -instance ctx of Context { ... } +inferred global for Universe { type T = String ... } +inferred ctx for Context { ... } ``` Then the following calls are all valid (and normalize to the last one) ```scala @@ -69,7 +69,7 @@ or as a sequence of types. To distinguish the two, a leading `(` always indicate ## Summoning an Instance -A method `summon` in `Predef` creates an implicit instance value for a given type, analogously to what `implicitly[T]` did. The only difference between the two is that +A method `summon` in `Predef` returns the inferred value for a given type, analogously to what `implicitly[T]` did. The only difference between the two is that `summon` takes a context parameter, where `implicitly` took an old-style implicit parameter: ```scala def summon[T] with (x: T) = x diff --git a/docs/docs/reference/instances/implicit-conversions.md b/docs/docs/reference/instances/implicit-conversions.md index b41bc675ec34..8e742c1a2cae 100644 --- a/docs/docs/reference/instances/implicit-conversions.md +++ b/docs/docs/reference/instances/implicit-conversions.md @@ -3,14 +3,14 @@ layout: doc-page title: "Implicit Conversions" --- -Implicit conversions are defined by instances of the `scala.Conversion` class. +Implicit conversions are defined by inferred 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 -instance of Conversion[String, Token] { +inferred for Conversion[String, Token] { def apply(str: String): Token = new KeyWord(str) } ``` @@ -33,7 +33,7 @@ If such an instance `C` is found, 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 -instance int2Integer of Conversion[Int, java.lang.Integer] { +inferred int2Integer for Conversion[Int, java.lang.Integer] { def apply(x: Int) = new java.lang.Integer(x) } ``` @@ -56,13 +56,13 @@ object Completions { // // CompletionArg.from(statusCode) - instance from of Conversion[String, CompletionArg] { + inferred from for Conversion[String, CompletionArg] { def apply(s: String) = CompletionArg.Error(s) } - instance from of Conversion[Future[HttpResponse], CompletionArg] { + inferred from for Conversion[Future[HttpResponse], CompletionArg] { def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) } - instance from of Conversion[Future[StatusCode], CompletionArg] { + inferred from for Conversion[Future[StatusCode], CompletionArg] { def apply(code: Future[StatusCode]) = CompletionArg.Status(code) } } diff --git a/docs/docs/reference/instances/implicit-function-types.md b/docs/docs/reference/instances/implicit-function-types.md index ab2373cbae1e..6876b5300ac6 100644 --- a/docs/docs/reference/instances/implicit-function-types.md +++ b/docs/docs/reference/instances/implicit-function-types.md @@ -3,31 +3,31 @@ layout: doc-page title: "Implicit Function Types and Closures" --- -An implicit function type describes functions with implicit (context) parameters. Example: +An implicit function type describes functions with inferred parameters. Example: ```scala type Contextual[T] = given Context => T ``` -A value of implicit function type is applied to context arguments, in -the same way a method with context parameters is applied. For instance: +A value of implicit function type is applied to inferred arguments, in +the same way a method with inferred parameters is applied. For instance: ```scala - implicit val ctx: Context = ... + inferred ctx for Context = ... def f(x: Int): Contextual[Int] = ... f(2) given ctx // explicit argument - f(2) // argument left implicit + 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 value, `E` is converted to an implicit function value +implicit function value, `E` is converted to an inferred function value by rewriting to ```scala given (x_1: T1, ..., x_n: Tn) => E ``` -where the names `x_1`, ..., `x_n` are arbitrary. Implicit closures are written -with a `given` prefix. They differ from normal closures in two ways: +where the names `x_1`, ..., `x_n` are arbitrary. inferred function values are written +with a `given` prefix. They differ from normal function values in two ways: - 1. Their parameters are implicit context parameters + 1. Their parameters are inferred parameters 2. Their types are implicit function types. For example, continuing with the previous definitions, diff --git a/docs/docs/reference/instances/instance-defs.md b/docs/docs/reference/instances/instance-defs.md index 8b1c70636601..532a2272b116 100644 --- a/docs/docs/reference/instances/instance-defs.md +++ b/docs/docs/reference/instances/instance-defs.md @@ -1,9 +1,10 @@ --- layout: doc-page -title: "Instance Definitions" +title: "Inferred Instances" --- -Instance definitions provide a concise and uniform syntax for defining implicit values. Example: +Inferred instance definitions provide a concise and uniform syntax for defining values +that can be inferred as implicit arguments. Example: ```scala trait Ord[T] { @@ -12,12 +13,12 @@ trait Ord[T] { def (x: T) > (y: T) = x.compareTo(y) > 0 } -instance IntOrd of Ord[Int] { +inferred IntOrd for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 } -instance ListOrd[T: Ord] of Ord[List[T]] { +inferred ListOrd[T: Ord] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -28,8 +29,8 @@ instance ListOrd[T: Ord] of Ord[List[T]] { } } ``` -Instance definitions can be seen as shorthands for what is currently expressed with implicit object and method definitions. -For example, the definition of instance `IntOrd` above defines an implicit value of type `Ord[Int]`. It is hence equivalent +Inferred instance definitions can be seen as shorthands for what is currently expressed with implicit object and method definitions. +For example, the definition of the inferred instance `IntOrd` above defines an implicit value of type `Ord[Int]`. It is hence equivalent to the following implicit object definition: ```scala implicit object IntOrd extends Ord[Int] { @@ -37,7 +38,7 @@ implicit object IntOrd extends Ord[Int] { if (x < y) -1 else if (x > y) +1 else 0 } ``` -The definition of instance `ListOrd` defines an ordering for `List[T]` provided there is an ordering for type `T`. With existing +The definition of the inferred instance `ListOrd` defines an ordering for `List[T]` provided there is an ordering for type `T`. With existing implicits, this could be expressed as a pair of a class and an implicit method: ```scala class ListOrd[T: Ord] extends Ord[List[T]] { @@ -52,54 +53,54 @@ class ListOrd[T: Ord] extends Ord[List[T]] { } implicit def ListOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] ``` -## Instances for Extension Methods +## Inferred Instances for Extension Methods -Instances can also be defined without an `of` clause. A typical application is to use a instance to package some extension methods. Examples: +Inferred instances can also be defined without a `for` clause. A typical application is to use an inferred instance to package some extension methods. Examples: ```scala -instance StringOps { +inferred StringOps { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } -instance ListOps { +inferred ListOps { def (xs: List[T]) second[T] = xs.tail.head } ``` -## Anonymous Instances +## Anonymous Inferred Instances -The name of an instance definition can be left out. Examples: +The name of an inferred instance can be left out. Examples: ```scala -instance of Ord[Int] { ... } -instance [T: Ord] of Ord[List[T]] { ... } +inferred for Ord[Int] { ... } +inferred [T: Ord] for Ord[List[T]] { ... } -instance { +inferred { def (xs: List[T]) second[T] = xs.tail.head } ``` -If the name of an instance is missing, the compiler will synthesize a name from +If the name of an inferred instance is missing, the compiler will synthesize a name from the type in the of clause, or, if that is missing, from the first defined extension method. -**Aside: ** Why anonymous instances? +**Aside: ** Why anonymous inferred instances? - It avoids clutter, relieving the programmer from having to invent names that are never referred to. Usually the invented names are either meaning less (e.g. `ev1`), or they just rephrase the implemented type. - - It gives a systematic foundation for synthesized instance definitions, such as those coming from a `derives` clause. + - It gives a systematic foundation for synthesized inferred instance definitions, such as those coming from a `derives` clause. - It achieves a uniform principle that the name of an implicit is always optional, no matter - whether the implicit is an instance definition or an implicit parameter. + whether the implicit is an inferred instance definition or an implicit parameter. -## Conditional Implicits +## Conditional Instances -An instance definition can depend on another instance being defined. Example: +An inferred instance definition can depend on another inferred instance being defined. Example: ```scala trait Conversion[-From, +To] { def apply(x: From): To } -instance [S, T] given (c: Conversion[S, T]) of Conversion[List[S], List[T]] { +inferred [S, T] given (c: Conversion[S, T]) for Conversion[List[S], List[T]] { def convert(x: List[From]): List[To] = x.map(c.apply) } ``` @@ -107,15 +108,15 @@ This defines an implicit conversion from `List[S]` to `List[T]` provided there i The `given` clause defines required instances. The `Conversion[List[From], List[To]]` instance above is defined only if a `Conversion[From, To]` instance exists. -Context bounds in instance definitions also translate to implicit parameters, -and therefore they can be represented alternatively as with clauses. For example, +Context bounds in inferred instance definitions also translate to implicit parameters, +and therefore they can be represented alternatively as `given` clauses. For example, here is an equivalent definition of the `ListOrd` instance: ```scala -instance ListOrd[T] given (ord: Ord[T]) of List[Ord[T]] { ... } +inferred ListOrd[T] given (ord: Ord[T]) for List[Ord[T]] { ... } ``` The name of a parameter in a `given` clause can also be left out, as shown in the following variant of `ListOrd`: ```scala -instance ListOrd[T] given Ord[T] of List[Ord[T]] { ... } +inferred ListOrd[T] given Ord[T] for List[Ord[T]] { ... } ``` As usual one can then infer to implicit parameter only indirectly, by passing it as implicit argument to another function. @@ -137,7 +138,7 @@ object Monoid { def apply[T] = implicitly[Monoid[T]] } -instance of Monoid[String] { +inferred for Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -158,50 +159,51 @@ trait Monad[F[_]] extends Functor[F] { def pure[A](x: A): F[A] } -instance ListMonad of Monad[List] { +inferred ListMonad for Monad[List] { def (xs: List[A]) flatMap[A, B] (f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } -instance ReaderMonad[Ctx] of Monad[[X] => Ctx => X] { +inferred ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { def (r: Ctx => A) flatMap[A, B] (f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = ctx => x } ``` -## Alias Instances +## Inferred Alias Instances -An alias instance creates an instance that is equal to some expression. E.g., +An inferred alias instance creates an inferred instance that is equal to +some expression. E.g., ``` -instance ctx of ExecutionContext = currentThreadPool().context +inferred ctx for ExecutionContext = currentThreadPool().context ``` -Here, we create an instance `ctx` of type `ExecutionContext` that resolves to the -right hand side `currentThreadPool().context`. Each time an instance of `ExecutionContext` +Here, we create an inferred instance `ctx` of type `ExecutionContext` that resolves to the +right hand side `currentThreadPool().context`. Each time an inferred instance of `ExecutionContext` is demanded, the result of evaluating the right-hand side expression is returned. The instance definition is equivalent to the following implicit definition: ``` final implicit def ctx: ExecutionContext = currentThreadPool().context ``` Alias instances may be anonymous, e.g. ``` -instance of Position = enclosingTree.position +inferred for Position = enclosingTree.position ``` -An alias instance can have type and context parameters just like any other instance definition, but it can only implement a single type. +An inferred alias instance can have type and context parameters just like any other inferred instance definition, but it can only implement a single type. ## Syntax -Here is the new syntax of instance definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). +Here is the new syntax of inferred instance definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). ``` TmplDef ::= ... - | ‘instance’ InstanceDef + | ‘inferred’ InstanceDef InstanceDef ::= [id] InstanceParams InstanceBody InstanceParams ::= [DefTypeParamClause] {InstParamClause} InstParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) -InstanceBody ::= [‘of’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] - | ‘of’ Type ‘=’ Expr +InstanceBody ::= [‘for’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] + | ‘for’ Type ‘=’ Expr ContextTypes ::= RefinedType {‘,’ RefinedType} ``` -The identifier `id` can be omitted only if either the `of` part or the template body is present. -If the `of` part is missing, the template body must define at least one extension method. +The identifier `id` can be omitted only if either the `for` part or the template body is present. +If the `for` part is missing, the template body must define at least one extension method. diff --git a/tests/pos/reference/instances.scala b/tests/pos/reference/instances.scala index a4e05e1b3564..59f6cbd21ff9 100644 --- a/tests/pos/reference/instances.scala +++ b/tests/pos/reference/instances.scala @@ -32,12 +32,12 @@ class Common { object Instances extends Common { - instance IntOrd of Ord[Int] { + inferred IntOrd for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 } - instance ListOrd[T] given Ord[T] of Ord[List[T]] { + inferred ListOrd[T] given Ord[T] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -48,25 +48,25 @@ object Instances extends Common { } } - instance StringOps { + inferred StringOps { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } - instance ListOps { + inferred ListOps { def (xs: List[T]) second[T] = xs.tail.head } - instance ListMonad of Monad[List] { + inferred ListMonad for Monad[List] { def (xs: List[A]) flatMap[A, B] (f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } - instance ReaderMonad[Ctx] of Monad[[X] => Ctx => X] { + inferred ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { def (r: Ctx => A) flatMap[A, B] (f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = @@ -105,7 +105,7 @@ object Instances extends Common { def (sym: Symbol) name: String } def symDeco: SymDeco - instance of SymDeco = symDeco + inferred for SymDeco = symDeco } object TastyImpl extends TastyAPI { type Symbol = String @@ -119,20 +119,20 @@ object Instances extends Common { class C given (ctx: Context) { def f() = { locally { - instance of Context = this.ctx + inferred for Context = this.ctx println(summon[Context].value) } locally { lazy val ctx1 = this.ctx - instance of Context = ctx1 + inferred for Context = ctx1 println(summon[Context].value) } locally { - instance d[T] of D[T] + inferred d[T] for D[T] println(summon[D[Int]]) } locally { - instance given Context of D[Int] + inferred given Context for D[Int] println(summon[D[Int]]) } } @@ -140,7 +140,7 @@ object Instances extends Common { class Token(str: String) - instance StringToToken of Conversion[String, Token] { + inferred StringToToken for Conversion[String, Token] { def apply(str: String): Token = new Token(str) } @@ -150,14 +150,14 @@ object Instances extends Common { object PostConditions { opaque type WrappedResult[T] = T - private instance WrappedResult { + private object WrappedResult { def apply[T](x: T): WrappedResult[T] = x def (x: WrappedResult[T]) unwrap[T]: T = x } def result[T] given (wrapped: WrappedResult[T]): T = wrapped.unwrap - instance { + inferred { def (x: T) ensuring[T] (condition: given WrappedResult[T] => Boolean): T = { assert(condition given WrappedResult(x)) x @@ -166,12 +166,12 @@ object PostConditions { } object AnonymousInstances extends Common { - instance of Ord[Int] { + inferred for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 } - instance [T: Ord] of Ord[List[T]] { + inferred [T: Ord] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -182,22 +182,22 @@ object AnonymousInstances extends Common { } } - instance { + inferred { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } - instance { + inferred { def (xs: List[T]) second[T] = xs.tail.head } - instance [From, To] given (c: Convertible[From, To]) of Convertible[List[From], List[To]] { + inferred [From, To] given (c: Convertible[From, To]) for Convertible[List[From], List[To]] { def (x: List[From]) convert: List[To] = x.map(c.convert) } - instance of Monoid[String] { + inferred for Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -272,13 +272,13 @@ object Completions { } // conversions defining the possible arguments to pass to `complete` - instance stringArg of Conversion[String, CompletionArg] { + inferred stringArg for Conversion[String, CompletionArg] { def apply(s: String) = CompletionArg.Error(s) } - instance responseArg of Conversion[Future[HttpResponse], CompletionArg] { + inferred responseArg for Conversion[Future[HttpResponse], CompletionArg] { def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) } - instance statusArg of Conversion[Future[StatusCode], CompletionArg] { + inferred statusArg for Conversion[Future[StatusCode], CompletionArg] { def apply(code: Future[StatusCode]) = CompletionArg.Status(code) } } \ No newline at end of file diff --git a/tests/run/tagless.scala b/tests/run/tagless.scala index 952aa3e44274..99f0440cc877 100644 --- a/tests/run/tagless.scala +++ b/tests/run/tagless.scala @@ -40,13 +40,13 @@ object Test extends App { add(lit(8), neg(add(lit(1), lit(2)))) // Base operations as typeclasses - instance of Exp[Int] { + inferred for Exp[Int] { def lit(i: Int): Int = i def neg(t: Int): Int = -t def add(l: Int, r: Int): Int = l + r } - instance of Exp[String] { + inferred for Exp[String] { def lit(i: Int): String = i.toString def neg(t: String): String = s"(-$t)" def add(l: String, r: String): String = s"($l + $r)" @@ -67,11 +67,11 @@ object Test extends App { def tfm1[T: Exp : Mult] = add(lit(7), neg(mul(lit(1), lit(2)))) def tfm2[T: Exp : Mult] = mul(lit(7), tf1) - instance of Mult[Int] { + inferred for Mult[Int] { def mul(l: Int, r: Int): Int = l * r } - instance of Mult[String] { + inferred for Mult[String] { def mul(l: String, r: String): String = s"$l * $r" } @@ -87,7 +87,7 @@ object Test extends App { } import Tree._ - instance of Exp[Tree], Mult[Tree] { + inferred for Exp[Tree], Mult[Tree] { def lit(i: Int): Tree = Node("Lit", Leaf(i.toString)) def neg(t: Tree): Tree = Node("Neg", t) def add(l: Tree, r: Tree): Tree = Node("Add", l , r) @@ -108,7 +108,7 @@ object Test extends App { private class Exc(msg: String) extends Exception(msg) def _throw(msg: String) given CanThrow: Nothing = throw new Exc(msg) def _try[T](op: Maybe[T])(handler: String => T): T = { - instance of CanThrow + inferred for CanThrow try op catch { case ex: Exception => handler(ex.getMessage) @@ -153,7 +153,7 @@ object Test extends App { def value[T] given Exp[T]: T } - instance of Exp[Wrapped] { + inferred for Exp[Wrapped] { def lit(i: Int) = new Wrapped { def value[T] given (e: Exp[T]): T = e.lit(i) } @@ -196,7 +196,7 @@ object Test extends App { // Added operation: negation pushdown enum NCtx { case Pos, Neg } - instance [T] given (e: Exp[T]) of Exp[NCtx => T] { + inferred [T] given (e: Exp[T]) for Exp[NCtx => T] { import NCtx._ def lit(i: Int) = { case Pos => e.lit(i) @@ -216,7 +216,7 @@ object Test extends App { println(pushNeg(tf1[NCtx => String])) println(pushNeg(pushNeg(pushNeg(tf1))): String) - instance [T] given (e: Mult[T]) of Mult[NCtx => T] { + inferred [T] given (e: Mult[T]) for Mult[NCtx => T] { import NCtx._ def mul(l: NCtx => T, r: NCtx => T): NCtx => T = { case Pos => e.mul(l(Pos), r(Pos)) @@ -230,7 +230,7 @@ object Test extends App { import IExp._ // Going from type class encoding to ADT encoding - instance initialize of Exp[IExp] { + inferred initialize for Exp[IExp] { def lit(i: Int): IExp = Lit(i) def neg(t: IExp): IExp = Neg(t) def add(l: IExp, r: IExp): IExp = Add(l, r) From af796f79f23f49163b41ff914014c7bb9a2cfc36 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 31 Jan 2019 00:06:46 +0100 Subject: [PATCH 08/16] Reorganize docs to consolidate all implicit changes --- .../dotty/tools/dotc/parsing/Parsers.scala | 8 +- docs/docs/internals/syntax.md | 18 +- .../reference/instances/context-bounds.md | 30 ++ .../reference/instances/context-params.md | 99 +++-- docs/docs/reference/instances/derivation.md | 389 ++++++++++++++++++ .../instances/discussion/instance-defs.md | 2 +- .../reference/instances/extension-methods.md | 157 +++++++ .../instances/implicit-by-name-parameters.md | 65 +++ .../instances/implicit-function-types.md | 43 +- .../docs/reference/instances/instance-defs.md | 165 +------- .../instances/relationship-implicits.md | 152 +++++++ docs/docs/reference/instances/typeclasses.md | 64 +++ .../other-new-features/extension-methods.md | 127 +----- docs/sidebar.yml | 39 +- library/src/dotty/DottyPredef.scala | 2 +- tests/pos/implicit-conversion.scala | 6 + tests/pos/reference/instances.scala | 10 +- 17 files changed, 1034 insertions(+), 342 deletions(-) create mode 100644 docs/docs/reference/instances/context-bounds.md create mode 100644 docs/docs/reference/instances/derivation.md create mode 100644 docs/docs/reference/instances/extension-methods.md create mode 100644 docs/docs/reference/instances/implicit-by-name-parameters.md create mode 100644 docs/docs/reference/instances/relationship-implicits.md create mode 100644 docs/docs/reference/instances/typeclasses.md create mode 100644 tests/pos/implicit-conversion.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d23b65909224..88ce23acdddc 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2018,8 +2018,8 @@ object Parsers { /** ClsParamClause ::= [nl | ‘with’] `(' [FunArgMods] [ClsParams] ')' * ClsParams ::= ClsParam {`' ClsParam} * ClsParam ::= {Annotation} [{Modifier} (`val' | `var') | `inline'] Param - * DefParamClause ::= [nl] `(' [FunArgMods] [DefParams] ')' | InstParamClause - * InstParamClause ::= ‘given’ (‘(’ DefParams ‘)’ | ContextTypes) + * DefParamClause ::= [nl] `(' [FunArgMods] [DefParams] ')' | InferParamClause + * InferParamClause ::= ‘given’ (‘(’ DefParams ‘)’ | ContextTypes) * ContextTypes ::= RefinedType {`,' RefinedType} * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param @@ -2112,7 +2112,7 @@ object Parsers { /** ClsParamClauses ::= {ClsParamClause} * DefParamClauses ::= {DefParamClause} - * InstParamClauses ::= {InstParamClause} + * InferParamClauses ::= {InferParamClause} * * @return The parameter definitions */ @@ -2536,7 +2536,7 @@ object Parsers { } /** InstanceDef ::= [id] InstanceParams InstanceBody - * InstanceParams ::= [DefTypeParamClause] {InstParamClause} + * InstanceParams ::= [DefTypeParamClause] {InferParamClause} * InstanceBody ::= [‘of’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] * | ‘of’ Type ‘=’ Expr */ diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 07bc05822dd8..389b1d85922c 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -156,7 +156,7 @@ SimpleType ::= SimpleType TypeArgs | [‘-’ | ‘+’ | ‘~’ | ‘!’] StableId PrefixOp(expr, op) | Path ‘.’ ‘type’ SingletonTypeTree(p) | ‘(’ ArgTypes ‘)’ Tuple(ts) - | ‘_’ TypeBounds + | ‘_’ SubtypeBounds | Refinement RefinedTypeTree(EmptyTree, refinement) | SimpleLiteral SingletonTypeTree(l) ArgTypes ::= Type {‘,’ Type} @@ -169,8 +169,8 @@ TypeArgs ::= ‘[’ ArgTypes ‘]’ NamedTypeArg ::= id ‘=’ Type NamedArg(id, t) NamedTypeArgs ::= ‘[’ NamedTypeArg {‘,’ NamedTypeArg} ‘]’ nts Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ ds -TypeBounds ::= [‘>:’ Type] [‘<:’ Type] | INT TypeBoundsTree(lo, hi) -TypeParamBounds ::= TypeBounds {‘<%’ Type} {‘:’ Type} ContextBounds(typeBounds, tps) +SubtypeBounds ::= [‘>:’ Type] [‘<:’ Type] | INT TypeBoundsTree(lo, hi) +TypeParamBounds ::= SubtypeBounds {‘<%’ Type} {‘:’ Type} ContextBounds(typeBounds, tps) ``` ### Expressions @@ -281,11 +281,11 @@ DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ -TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds +TypTypeParam ::= {Annotation} id [HkTypeParamClause] SubtypeBounds HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | ‘_’) - TypeBounds + SubtypeBounds ClsParamClauses ::= {ClsParamClause} ClsParamClause ::= [nl] ‘(’ [[FunArgMods] ClsParams] ‘)’ @@ -297,8 +297,8 @@ Param ::= id ‘:’ ParamType [‘=’ Expr] | INT DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] -DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ | InstParamClause -InstParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) +DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ | InferParamClause +InferParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ContextTypes ::= RefinedType {‘,’ RefinedType} @@ -346,7 +346,7 @@ VarDcl ::= ids ‘:’ Type DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree) DefSig ::= ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses -TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds) +TypeDcl ::= id [TypeParamClause] (SubtypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds) | id [TypeParamClause] <: Type = MatchType Def ::= ‘val’ PatDef @@ -374,7 +374,7 @@ ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template) InstanceDef ::= [id] InstanceParams InstanceBody -InstanceParams ::= [DefTypeParamClause] {InstParamClause} +InstanceParams ::= [DefTypeParamClause] {InferParamClause} InstanceBody ::= [‘of’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] | ‘of’ Type ‘=’ Expr Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats) diff --git a/docs/docs/reference/instances/context-bounds.md b/docs/docs/reference/instances/context-bounds.md new file mode 100644 index 000000000000..195a766e6410 --- /dev/null +++ b/docs/docs/reference/instances/context-bounds.md @@ -0,0 +1,30 @@ +--- +layout: doc-page +title: "Context Bounds" +--- + +## Context Bounds + +A context bound is a shorthand for expressing a common pattern of implicit parameters. For example, the `maximum` function of the last section could also have been written like this: +```scala +def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max) +``` +A bound `: C` on a type parameter `T` of a method or class indicates an inferred parameter `given C[T]`. The inferred parameter(s) generated from context bounds come last in the definition of the containing method or class. E.g., +``` +def f[T: C1 : C2, U: C3](x: T) given (y: U, z: V): R +``` +would expand to +``` +def f[T, U](x: T) given (y: U, z: V) given 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. +``` +def g[T <: B : C](x: T): R = ... +``` + +## Syntax + +``` +TypeParamBounds ::= [SubtypeBounds] {ContextBound} +ContextBound ::= ‘:’ Type +``` diff --git a/docs/docs/reference/instances/context-params.md b/docs/docs/reference/instances/context-params.md index 5a5975bce751..dccddcc464e6 100644 --- a/docs/docs/reference/instances/context-params.md +++ b/docs/docs/reference/instances/context-params.md @@ -1,56 +1,76 @@ --- layout: doc-page -title: "Inferred Parameters and Arguments" +title: "Inferable Parameters and Arguments" --- -A new syntax for implicit parameters aligns definition and call syntax. Parameter definitions and method arguments both follow a `given` keyword. On the definition side, the old syntax -```scala -def f(a: A)(implicit b: B) +Functional programming tends to express most dependencies as simple functions 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. Inferable parameters can help here since they enable the compiler to synthesize +repetitive arguments instead of the programmer having to write them explicitly. + +For example, given the [instance definitions](./instance-definitions.md) defined previously, +a maximum function that works for any arguments for which an ordering exists can be defined as follows: ``` -is now expressed as -```scala -def f(a: A) given (b: B) +def max[T](x: T, y: T) given (ord: Ord[T]): T = + if (ord.compare(x, y) < 1) y else x ``` -or, leaving out the parameter name, -```scala -def f(a: A) given B +Here, `ord` is an _inferable parameter_. Inferable parameters are introduced with a `given` clause. Here is an example application of `max`: +``` +max(2, 3) given IntOrd ``` -Implicit parameters defined with the new syntax are also called _context parameters_. -They come with a matching syntax for applications: explicit arguments for context parameters are also written after a `given`. +The `given IntOrd` part provides the `IntOrd` instance as an argument for the `ord` parameter. But the point of inferable parameters is that this argument can also be left out (and usually is): +``` +max(2, 3) +``` +This is equally valid, and is completed by the compiler to the previous application. -The following example shows shows three methods that each have a context parameter for `Ord[T]`. -```scala +## Anonymous Inferred Parameters + +In many situations, the name of an inferable parameter of a method need not be +mentioned explicitly at all, since it is only used in synthesized arguments for +other inferable parameters. In that case one can avoid defining a parameter name +and just provide its type. Example: +``` def maximum[T](xs: List[T]) given Ord[T]: T = - xs.reduceLeft((x, y) => if (x < y) y else x) + xs.reduceLeft(max) +``` +`maximum` takes an inferable parameter of type `Ord` only to pass it on as an +inferred argument to `max`. The name of the parameter is left out. + +Generally, inferable parameters may be given either as a parameter list `(p_1: T_1, ..., p_n: T_n)` +or as a sequence of types, separated by commas. To distinguish the two, a leading +`(` always indicates a parameter list. + +## Synthesizing Complex Inferred Arguments +Here are two other methods that have an inferable parameter of type `Ord[T]`: +```scala def descending[T] given (asc: Ord[T]): Ord[T] = new Ord[T] { - def (x: T) compareTo (y: T) = asc.compareTo(y)(x) + 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)`. -But usually, explicit arguments for context parameters are be left out. For instance, +But usually, explicit arguments for inferable parameters are be left out. For instance, given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:) ```scala -maximum(xs) +minimum(xs) maximum(xs) given descending -maximum(xs) given (descending given IntOrd) +maximum(xs) given (descending given ListOrd) +maximum(xs) given (descending given (ListOrd given InOrd)) ``` -Arguments for context parameters must use the `given` syntax. So the expression `maximum(xs)(descending)` would produce a type error. +In summary, the argument passed in the definition of minimum is constructed +from the `descending` function applied to the argument `ListOrd`, which is +in turn applied to the argument `IntOrd`. -The `given` connective is treated like an infix operator with the same precedence as other operators that start with a letter. The expression following a `given` may also be an argument list consisting of several implicit arguments separated by commas. If a tuple should be passed as a single implicit argument (probably an uncommon case), it has to be put in a pair of extra parentheses: -```scala -def f given (x: A, y: B) -f given (a, b) +## Mixing Inferable And Normal Parameters -def g given (xy: (A, B)) -g given ((a, b)) -``` -Unlike existing implicit parameters, context parameters can be freely mixed with normal parameter lists. -A context parameter may be followed by a normal parameter and _vice versa_. There can be several context parameter -lists in a definition. Example: +Inferable parameters can be freely mixed with normal parameter lists. +An inferable parameter may be followed by a normal parameter and _vice versa_. +There can be several inferable parameter lists in a definition. Example: ```scala def f given (u: Universe) (x: u.T) given Context = ... @@ -64,15 +84,17 @@ f("abc") f("abc") given ctx (f given global)("abc") given ctx ``` -Context parameters may be given either as a normal parameter list `(...)` -or as a sequence of types. To distinguish the two, a leading `(` always indicates a parameter list. -## Summoning an Instance +## Summmoning an Inferred Instance -A method `summon` in `Predef` returns the inferred value for a given type, analogously to what `implicitly[T]` did. The only difference between the two is that -`summon` takes a context parameter, where `implicitly` took an old-style implicit parameter: +A method `infer` in `Predef` creates an instance value for a given type. For example, +the instance value for `Ord[List[Int]]` is generated by +``` +infer[Ord[List[Int]]] +``` +The `infer` method is simply defined as the identity function with an inferable parameter. ```scala -def summon[T] with (x: T) = x +def infer[T] given (x: T) = x ``` ## Syntax @@ -82,7 +104,10 @@ Here is the new syntax of parameters and arguments seen as a delta from the [sta ClsParamClause ::= ... | ‘given’ (‘(’ [ClsParams] ‘)’ | ContextTypes) DefParamClause ::= ... - | InstParamClause + | InferParamClause +InferParamClause ::= ‘given’ (‘(’ DefParams ‘)’ | ContextTypes) +ContextTypes ::= RefinedType {‘,’ RefinedType} + InfixExpr ::= ... | InfixExpr ‘given’ (InfixExpr | ParArgumentExprs) ``` diff --git a/docs/docs/reference/instances/derivation.md b/docs/docs/reference/instances/derivation.md new file mode 100644 index 000000000000..17c2221d64d2 --- /dev/null +++ b/docs/docs/reference/instances/derivation.md @@ -0,0 +1,389 @@ +--- +layout: doc-page +title: Typeclass Derivation +--- + +Typeclass derivation is a way to generate instances of certain type classes automatically or with minimal code hints. A type class in this sense is any trait or class with a type parameter that describes the type being operated on. Commonly used examples are `Eq`, `Ordering`, `Show`, or `Pickling`. Example: +```scala +enum Tree[T] derives Eq, Ordering, Pickling { + case Branch(left: Tree[T], right: Tree[T]) + case Leaf(elem: T) +} +``` +The `derives` clause generates inferred instances of the `Eq`, `Ordering`, and `Pickling` traits in the companion object `Tree`: +```scala +inferred [T: Eq] for Eq[Tree[T]] = Eq.derived +inferred [T: Ordering] for Ordering[Tree[T]] = Ordering.derived +inferred [T: Pickling] for Pickling[Tree[T]] = Pickling.derived +``` + +### Deriving Types + +Besides for `enums`, typeclasses can also be derived for other sets of classes and objects that form an algebraic data type. These are: + + - individual case classes or case objects + - sealed classes or traits that have only case classes and case objects as children. + + Examples: + + ```scala +case class Labelled[T](x: T, label: String) derives Eq, Show + +sealed trait Option[T] derives Eq +case class Some[T] extends Option[T] +case object None extends Option[Nothing] +``` + +The generated typeclass instances are placed in the companion objects `Labelled` and `Option`, respectively. + +### Derivable Types + +A trait or class can appear in a `derives` clause if + + - it has a single type parameter, and + - its companion object defines a method named `derived`. + +These two conditions ensure that the synthesized derived instances for the trait are well-formed. The type and implementation of a `derived` method are arbitrary, but typically it has a definition like this: +```scala + def derived[T] with Generic[T] = ... +``` +That is, the `derived` method takes an implicit parameter of type `Generic` that determines the _shape_ of the deriving type `T` and it computes the typeclass implementation according to that shape. A `Generic` instance is generated automatically +for any types that derives a typeclass that needs it. One can also derive `Generic` alone, which means a `Generic` instance is generated without any other type class instances. E.g.: +```scala +sealed trait ParseResult[T] derives Generic +``` +This is all a user of typeclass derivation has to know. The rest of this page contains information needed to be able to write a typeclass that can appear in a `derives` clause. In particular, it details the means provided for the implementation of data generic `derived` methods. + +### The Shape Type + +For every class with a `derives` clause, the compiler computes the shape of that class as a type. For example, here is the shape type for the `Tree[T]` enum: +```scala +Cases[( + Case[Branch[T], (Tree[T], Tree[T])], + Case[Leaf[T], T *: Unit] +)] +``` +Informally, this states that + +> The shape of a `Tree[T]` is one of two cases: Either a `Branch[T]` with two + elements of type `Tree[T]`, or a `Leaf[T]` with a single element of type `T`. + +The type constructors `Cases` and `Case` come from the companion object of a class +`scala.compiletime.Shape`, which is defined in the standard library as follows: +```scala +sealed abstract class Shape + +object Shape { + + /** A sum with alternative types `Alts` */ + case class Cases[Alts <: Tuple] extends Shape + + /** A product type `T` with element types `Elems` */ + case class Case[T, Elems <: Tuple] extends Shape +} +``` + +Here is the shape type for `Labelled[T]`: +```scala +Case[Labelled[T], (T, String)] +``` +And here is the one for `Option[T]`: +```scala +Cases[( + Case[Some[T], T *: Unit], + Case[None.type, Unit] +)] +``` +Note that an empty element tuple is represented as type `Unit`. A single-element tuple +is represented as `T *: Unit` since there is no direct syntax for such tuples: `(T)` is just `T` in parentheses, not a tuple. + +### The Generic TypeClass + +For every class `C[T_1,...,T_n]` with a `derives` clause, the compiler generates in the companion object of `C` an inferred instance of `Generic[C[T_1,...,T_n]]` that follows +the outline below: +```scala +inferred [T_1, ..., T_n] for Generic[C[T_1,...,T_n]] { + type Shape = ... + ... +} +``` +where the right hand side of `Shape` is the shape type of `C[T_1,...,T_n]`. +For instance, the definition +```scala +enum Result[+T, +E] derives Logging { + case class Ok[T](result: T) + case class Err[E](err: E) +} +``` +would produce: +```scala +object Result { + import scala.compiletime.Shape._ + + inferred [T, E] for Generic[Result[T, E]] { + type Shape = Cases[( + Case[Ok[T], T *: Unit], + Case[Err[E], E *: Unit] + )] + ... + } +} +``` +The `Generic` class is defined in package `scala.reflect`. + +```scala +abstract class Generic[T] { + type Shape <: scala.compiletime.Shape + + /** The mirror corresponding to ADT instance `x` */ + def reflect(x: T): Mirror + + /** The ADT instance corresponding to given `mirror` */ + def reify(mirror: Mirror): T + + /** The companion object of the ADT */ + def common: GenericClass +} +``` +It defines the `Shape` type for the ADT `T`, as well as two methods that map between a +type `T` and a generic representation of `T`, which we call a `Mirror`: +The `reflect` method maps an instance value of the ADT `T` to its mirror whereas +the `reify` method goes the other way. There's also a `common` method that returns +a value of type `GenericClass` which contains information that is the same for all +instances of a class (right now, this consists of the runtime `Class` value and +the names of the cases and their parameters). + +### Mirrors + +A mirror is a generic representation of an instance value of an ADT. `Mirror` objects have three components: + + - `adtClass: GenericClass`: The representation of the ADT class + - `ordinal: Int`: The ordinal number of the case among all cases of the ADT, starting from 0 + - `elems: Product`: The elements of the instance, represented as a `Product`. + + The `Mirror` class is defined in package `scala.reflect` as follows: + +```scala +class Mirror(val adtClass: GenericClass, val ordinal: Int, val elems: Product) { + + /** The `n`'th element of this generic case */ + def apply(n: Int): Any = elems.productElement(n) + + /** The name of the constructor of the case reflected by this mirror */ + def caseLabel: String = adtClass.label(ordinal)(0) + + /** The label of the `n`'th element of the case reflected by this mirror */ + def elementLabel(n: Int): String = adtClass.label(ordinal)(n + 1) +} +``` + +### GenericClass + +Here's the API of `scala.reflect.GenericClass`: + +```scala +class GenericClass(val runtimeClass: Class[_], labelsStr: String) { + + /** A mirror of case with ordinal number `ordinal` and elements as given by `Product` */ + def mirror(ordinal: Int, product: Product): Mirror = + new Mirror(this, ordinal, product) + + /** A mirror with elements given as an array */ + def mirror(ordinal: Int, elems: Array[AnyRef]): Mirror = + mirror(ordinal, new ArrayProduct(elems)) + + /** A mirror with an initial empty array of `numElems` elements, to be filled in. */ + def mirror(ordinal: Int, numElems: Int): Mirror = + mirror(ordinal, new Array[AnyRef](numElems)) + + /** A mirror of a case with no elements */ + def mirror(ordinal: Int): Mirror = + mirror(ordinal, EmptyProduct) + + /** Case and element labels as a two-dimensional array. + * Each row of the array contains a case label, followed by the labels of the elements of that case. + */ + val label: Array[Array[String]] = ... +} +``` + +The class provides four overloaded methods to create mirrors. The first of these is invoked by the `reify` method that maps an ADT instance to its mirror. It simply passes the +instance itself (which is a `Product`) to the second parameter of the mirror. That operation does not involve any copying and is thus quite efficient. The second and third versions of `mirror` are typically invoked by typeclass methods that create instances from mirrors. An example would be an `unpickle` method that first creates an array of elements, then creates +a mirror over that array, and finally uses the `reify` method in `Reflected` to create the ADT instance. The fourth version of `mirror` is used to create mirrors of instances that do not have any elements. + +### How to Write Generic Typeclasses + +Based on the machinery developed so far it becomes possible to define type classes generically. This means that the `derived` method will compute a type class instance for any ADT that has a `Generic` instance, recursively. +The implementation of these methods typically uses three new type-level constructs in Dotty: inline methods, inline matches, and implicit matches. As an example, here is one possible implementation of a generic `Eq` type class, with explanations. Let's assume `Eq` is defined by the following trait: +```scala +trait Eq[T] { + def eql(x: T, y: T): Boolean +} +``` +We need to implement a method `Eq.derived` that produces an instance of `Eq[T]` provided +there exists evidence of type `Generic[T]`. Here's a possible solution: +```scala + inline def derived[T] with (ev: Generic[T]): Eq[T] = new Eq[T] { + def eql(x: T, y: T): Boolean = { + val mx = ev.reflect(x) // (1) + val my = ev.reflect(y) // (2) + inline erasedValue[ev.Shape] match { + case _: Cases[alts] => + mx.ordinal == my.ordinal && // (3) + eqlCases[alts](mx, my, 0) // [4] + case _: Case[_, elems] => + eqlElems[elems](mx, my, 0) // [5] + } + } + } +``` +The implementation of the inline method `derived` creates an instance of `Eq[T]` and implements its `eql` method. The right-hand side of `eql` mixes compile-time and runtime elements. In the code above, runtime elements are marked with a number in parentheses, i.e +`(1)`, `(2)`, `(3)`. Compile-time calls that expand to runtime code are marked with a number in brackets, i.e. `[4]`, `[5]`. The implementation of `eql` consists of the following steps. + + 1. Map the compared values `x` and `y` to their mirrors using the `reflect` method of the implicitly passed `Generic` evidence `(1)`, `(2)`. + 2. Match at compile-time against the shape of the ADT given in `ev.Shape`. Dotty does not have a construct for matching types directly, but we can emulate it using an `inline` match over an `erasedValue`. Depending on the actual type `ev.Shape`, the match will reduce at compile time to one of its two alternatives. + 3. If `ev.Shape` is of the form `Cases[alts]` for some tuple `alts` of alternative types, the equality test consists of comparing the ordinal values of the two mirrors `(3)` and, if they are equal, comparing the elements of the case indicated by that ordinal value. That second step is performed by code that results from the compile-time expansion of the `eqlCases` call `[4]`. + 4. If `ev.Shape` is of the form `Case[elems]` for some tuple `elems` for element types, the elements of the case are compared by code that results from the compile-time expansion of the `eqlElems` call `[5]`. + +Here is a possible implementation of `eqlCases`: +```scala + inline def eqlCases[Alts <: Tuple](mx: Mirror, my: Mirror, n: Int): Boolean = + inline erasedValue[Alts] match { + case _: (Shape.Case[_, elems] *: alts1) => + if (mx.ordinal == n) // (6) + eqlElems[elems](mx, my, 0) // [7] + else + eqlCases[alts1](mx, my, n + 1) // [8] + case _: Unit => + throw new MatchError(mx.ordinal) // (9) + } +``` +The inline method `eqlCases` takes as type arguments the alternatives of the ADT that remain to be tested. It takes as value arguments mirrors of the two instances `x` and `y` to be compared and an integer `n` that indicates the ordinal number of the case that is tested next. It produces an expression that compares these two values. + +If the list of alternatives `Alts` consists of a case of type `Case[_, elems]`, possibly followed by further cases in `alts1`, we generate the following code: + + 1. Compare the `ordinal` value of `mx` (a runtime value) with the case number `n` (a compile-time value translated to a constant in the generated code) in an if-then-else `(6)`. + 2. In the then-branch of the conditional we have that the `ordinal` value of both mirrors + matches the number of the case with elements `elems`. Proceed by comparing the elements + of the case in code expanded from the `eqlElems` call `[7]`. + 3. In the else-branch of the conditional we have that the present case does not match + the ordinal value of both mirrors. Proceed by trying the remaining cases in `alts1` using + code expanded from the `eqlCases` call `[8]`. + + If the list of alternatives `Alts` is the empty tuple, there are no further cases to check. + This place in the code should not be reachable at runtime. Therefore an appropriate + implementation is by throwing a `MatchError` or some other runtime exception `(9)`. + +The `eqlElems` method compares the elements of two mirrors that are known to have the same +ordinal number, which means they represent the same case of the ADT. Here is a possible +implementation: +```scala + inline def eqlElems[Elems <: Tuple](xs: Mirror, ys: Mirror, n: Int): Boolean = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + tryEql[elem]( // [12] + xs(n).asInstanceOf[elem], // (10) + ys(n).asInstanceOf[elem]) && // (11) + eqlElems[elems1](xs, ys, n + 1) // [13] + case _: Unit => + true // (14) + } +``` +`eqlElems` takes as arguments the two mirrors of the elements to compare and a compile-time index `n`, indicating the index of the next element to test. It is defined in terms of another compile-time match, this time over the tuple type `Elems` of all element types that remain to be tested. If that type is +non-empty, say of form `elem *: elems1`, the following code is produced: + + 1. Access the `n`'th elements of both mirrors and cast them to the current element type `elem` + `(10)`, `(11)`. Note that because of the way runtime reflection mirrors compile-time `Shape` types, the casts are guaranteed to succeed. + 2. Compare the element values using code expanded by the `tryEql` call `[12]`. + 3. "And" the result with code that compares the remaining elements using a recursive call + to `eqlElems` `[13]`. + + If type `Elems` is empty, there are no more elements to be compared, so the comparison's result is `true`. `(14)` + + Since `eqlElems` is an inline method, its recursive calls are unrolled. The end result is a conjunction `test_1 && ... && test_n && true` of test expressions produced by the `tryEql` calls. + +The last, and in a sense most interesting part of the derivation is the comparison of a pair of element values in `tryEql`. Here is the definition of this method: +```scala + inline def tryEql[T](x: T, y: T) = implicit match { + case ev: Eq[T] => + ev.eql(x, y) // (15) + case _ => + error("No `Eq` instance was found for $T") + } +``` +`tryEql` is an inline method that takes an element type `T` and two element values of that type as arguments. It is defined using an `inline match` that tries to find an implicit instance of `Eq[T]`. If an instance `ev` is found, it proceeds by comparing the arguments using `ev.eql`. On the other hand, if no instance is found +this signals a compilation error: the user tried a generic derivation of `Eq` for a class with an element type that does not support an `Eq` instance itself. The error is signaled by +calling the `error` method defined in `scala.compiletime`. + +**Note:** At the moment our error diagnostics for metaprogramming does not support yet interpolated string arguments for the `scala.compiletime.error` method that is called in the second case above. As an alternative, one can simply leave off the second case, then a missing typeclass would result in a "failure to reduce match" error. + +**Example:** Here is a slightly polished and compacted version of the code that's generated by inline expansion for the derived `Eq` instance of class `Tree`. + +```scala +inferred [T] with (elemEq: Eq[T]) for Eq[Tree[T]] { + def eql(x: Tree[T], y: Tree[T]): Boolean = { + val ev = infer[Generic[Tree[T]]] + val mx = ev.reflect(x) + val my = ev.reflect(y) + mx.ordinal == my.ordinal && { + if (mx.ordinal == 0) { + this.eql(mx(0).asInstanceOf[Tree[T]], my(0).asInstanceOf[Tree[T]]) && + this.eql(mx(1).asInstanceOf[Tree[T]], my(1).asInstanceOf[Tree[T]]) + } + else if (mx.ordinal == 1) { + elemEq.eql(mx(0).asInstanceOf[T], my(0).asInstanceOf[T]) + } + else throw new MatchError(mx.ordinal) + } + } +} +``` + +One important difference between this approach and Scala-2 typeclass derivation frameworks such as Shapeless or Magnolia is that no automatic attempt is made to generate typeclass instances of elements recursively using the generic derivation framework. There must be an implicit instance of `Eq[T]` (which can of course be produced in turn using `Eq.derived`), or the compilation will fail. The advantage of this more restrictive approach to typeclass derivation is that it avoids uncontrolled transitive typeclass derivation by design. This keeps code sizes smaller, compile times lower, and is generally more predictable. + +### Derived Instances Elsewhere + +Sometimes one would like to derive a typeclass 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 with the `derived` method of the typeclass as right-hand side. E.g, to implement `Ordering` for `Option`, define: +```scala +inferred [T: Ordering]: Ordering[Option[T]] = Ordering.derived +``` +Usually, the `Ordering.derived` clause has an implicit parameter of type +`Generic[Option[T]]`. Since the `Option` trait has a `derives` clause, +the necessary inferred instance is already present in the companion object of `Option`. +If the ADT in question does not have a `derives` clause, an implicit `Generic` instance +would still be synthesized by the compiler at the point where `derived` is called. +This is similar to the situation with type tags or class tags: If no implicit instance +is found, the compiler will synthesize one. + +### Syntax + +``` +Template ::= InheritClauses [TemplateBody] +EnumDef ::= id ClassConstr InheritClauses EnumBody +InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] +ConstrApps ::= ConstrApp {‘with’ ConstrApp} + | ConstrApp {‘,’ ConstrApp} +``` + +### Discussion + +The typeclass derivation framework is quite small and low-level. There are essentially +two pieces of infrastructure in the compiler-generated `Generic` instances: + + - a type representing the shape of an ADT, + - a way to map between ADT instances and generic mirrors. + +Generic mirrors make use of the already existing `Product` infrastructure for case +classes, which means they are efficient and their generation requires not much code. + +Generic mirrors can be so simple because, just like `Product`s, they are weakly +typed. On the other hand, this means that code for generic typeclasses 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 typeclasses +are correctly written these casts will never fail. + +It could make sense to explore a higher-level framework that encapsulates all casts +in the framework. This could give more guidance to the typeclass implementer. +It also seems quite possible to put such a framework on top of the lower-level +mechanisms presented here. diff --git a/docs/docs/reference/instances/discussion/instance-defs.md b/docs/docs/reference/instances/discussion/instance-defs.md index d0968ad03144..0d9e8e828dee 100644 --- a/docs/docs/reference/instances/discussion/instance-defs.md +++ b/docs/docs/reference/instances/discussion/instance-defs.md @@ -144,7 +144,7 @@ instance of Monoid[String] { } def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) + xs.foldLeft(infer[Monoid[T]].unit)(_.combine(_)) ``` Functors and monads: ```scala diff --git a/docs/docs/reference/instances/extension-methods.md b/docs/docs/reference/instances/extension-methods.md new file mode 100644 index 000000000000..376b14736ec3 --- /dev/null +++ b/docs/docs/reference/instances/extension-methods.md @@ -0,0 +1,157 @@ +--- +layout: doc-page +title: "Extension Methods" +--- + +Extension methods allow one to add methods to a type after the type is defined. Example: + +```scala +case class Circle(x: Double, y: Double, radius: Double) + +def (c: Circle) circumference: Double = c.radius * math.Pi * 2 +``` + +Like regular methods, extension methods can be invoked with infix `.`: + +```scala + val circle = Circle(0, 0, 1) + circle.circumference +``` + +### Translation of Extension Methods + +Extension methods are methods that have a parameter clause in front of the defined +identifier. They translate to methods where the leading parameter section is moved +to after the defined identifier. So, the definition of `circumference` above translates +to the plain method, and can also be invoked as such: +```scala +def circumference(c: Circle): Double = c.radius * math.Pi * 2 + +assert(circle.circumference == circumference(circle)) +``` + +### Translation of Calls to Extension Methods + +When is an extension method applicable? There are two possibilities. + + - An extension method is applicable if it is visible under a simple name, by being defined + or inherited or imported in a scope enclosing the application. + - An extension method is applicable if it is a member of an eligible implicit value at the point of the application. + +As an example, consider an extension method `longestStrings` on `String` defined in a trait `StringSeqOps`. + +```scala +trait StringSeqOps { + def (xs: Seq[String]) longestStrings = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} +``` +We can make the extension method available by defining an implicit instance of `StringSeqOps`, like this: +```scala +instance StringSeqOps1 of StringSeqOps +``` +Then +```scala +List("here", "is", "a", "list").longestStrings +``` +is legal everywhere `StringSeqOps1` is available as an implicit value. Alternatively, we can define `longestStrings` +as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. + +```scala +object ops2 extends StringSeqOps +import ops2.longestStrings +List("here", "is", "a", "list").longestStrings +``` +The precise rules for resolving a selection to an extension method are as follows. + +Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, +and where `T` is the expected type. The following two rewritings are tried in order: + + 1. The selection is rewritten to `m[Ts](e)`. + 2. If the first rewriting does not typecheck with expected type `T`, and there is an implicit value `i` + in either the current scope or in the implicit scope of `T`, and `i` defines an extension + method named `m`, then selection is expanded to `i.m[Ts](e)`. + 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. + +So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided +`circle` has type `Circle` and `CircleOps` is an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). + +## Instances for Extension Methods + +Instances that define extension methods can also be defined without an `of` clause. E.g., + +```scala +instance StringOps { + def (xs: Seq[String]) longestStrings: Seq[String] = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} + +instance ListOps { + def (xs: List[T]) second[T] = xs.tail.head +} +``` +If such instances are anonymous (as in the examples above), their name is synthesized from the name +of the first defined extension method. + +### Operators + +The extension method syntax also applies to the definition of operators. +In each case the definition syntax mirrors the way the operator is applied. +Examples: +``` + def (x: String) < (y: String) = ... + def (x: Elem) +: (xs: Seq[Elem]) = ... + + "ab" + "c" + 1 +: List(2, 3) +``` +The two definitions above translate to +``` + def < (x: String)(y: String) = ... + def +: (xs: Seq[Elem])(x: Elem) = ... +``` +Note that swap of the two parameters `x` and `xs` when translating +the right-binding operator `+:` to an extension method. This is analogous +to the implementation of right binding operators as normal methods. + +### Generic Extensions + +The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible to extend a generic type by adding type parameters to an extension method: + +```scala +def (xs: List[T]) second [T] = xs.tail.head +``` + +or: + + +```scala +def (xs: List[List[T]]) flattened [T] = xs.foldLeft[List[T]](Nil)(_ ++ _) +``` + +or: + +```scala +def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y) +``` + +As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the preceding parameter clause. + + +### Syntax + +The required syntax extension just adds one clause for extension methods relative +to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). +``` +DefSig ::= ... + | ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses +``` + + + + diff --git a/docs/docs/reference/instances/implicit-by-name-parameters.md b/docs/docs/reference/instances/implicit-by-name-parameters.md new file mode 100644 index 000000000000..815eae1a483b --- /dev/null +++ b/docs/docs/reference/instances/implicit-by-name-parameters.md @@ -0,0 +1,65 @@ +--- +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 +} + +inferred intCodec for Codec[Int] = ??? + +inferred optionCodec[T] given (ev: => Codec[T]) for Codec[Option[T]] { + def write(xo: Option[T]) = xo match { + case Some(x) => ev.write(x) + case None => + } +} + +val s = infer[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 constructing an implicit argument for a by-name parameter of type `=> T` are as follows. + + 1. Create a new inferred instance of type `T`: + + ```scala + inferred for T = ??? + ``` + + 1. This instance 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 inferred instance created previously, replace `E` by + + + ```scala + { inferred for T = E; lv } + ``` + + Otherwise, return `E` unchanged. + +In the example above, the definition of `s` would be expanded as follows. + +```scala +val s = infer[Test.Codec[Option[Int]]]( + optionCodec[Int](intCodec)) +``` + +No local 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/instances/implicit-function-types.md b/docs/docs/reference/instances/implicit-function-types.md index 6876b5300ac6..2f0631fc4ce3 100644 --- a/docs/docs/reference/instances/implicit-function-types.md +++ b/docs/docs/reference/instances/implicit-function-types.md @@ -1,6 +1,6 @@ --- layout: doc-page -title: "Implicit Function Types and Closures" +title: "Implicit Function Types and Values" --- An implicit function type describes functions with inferred parameters. Example: @@ -24,7 +24,7 @@ by rewriting to ```scala given (x_1: T1, ..., x_n: Tn) => E ``` -where the names `x_1`, ..., `x_n` are arbitrary. inferred function values are written +where the names `x_1`, ..., `x_n` are arbitrary. Inferred function values are written with a `given` prefix. They differ from normal function values in two ways: 1. Their parameters are inferred parameters @@ -40,6 +40,8 @@ For example, continuing with the previous definitions, 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: @@ -104,6 +106,43 @@ With that setup, the table construction code above compiles and expands to: } given $t } ``` +### Example: Postconditions + +As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extensions to provide a zero-overhead abstraction. + +```scala +object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T] given (r: WrappedResult[T]): T = WrappedResult.unwrap(r) + + def (x: T) ensuring [T](condition: given WrappedResult[T] => Boolean): T = { + inferred for WrappedResult[T] = WrappedResult.wrap(x) + assert(condition) + x + } +} + +object Test { + import PostConditions.ensuring + 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 an implicit value 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 inferred instances in scope (this is good practice in all cases where inferred 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: + + { 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), diff --git a/docs/docs/reference/instances/instance-defs.md b/docs/docs/reference/instances/instance-defs.md index 532a2272b116..35aeccacae03 100644 --- a/docs/docs/reference/instances/instance-defs.md +++ b/docs/docs/reference/instances/instance-defs.md @@ -3,176 +3,49 @@ layout: doc-page title: "Inferred Instances" --- -Inferred instance definitions provide a concise and uniform syntax for defining values -that can be inferred as implicit arguments. Example: +Inferred instance definitions define "canonical" values of given types +that serve for synthesizing arguments to [inferable parameters](./inferable-params.html). Example: ```scala trait Ord[T] { - def (x: T) compareTo (y: T): Int - def (x: T) < (y: T) = x.compareTo(y) < 0 - def (x: T) > (y: T) = x.compareTo(y) > 0 + 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 } inferred IntOrd for Ord[Int] { - def (x: Int) compareTo (y: Int) = + def compare(x: Int, y: Int) = if (x < y) -1 else if (x > y) +1 else 0 } inferred ListOrd[T: Ord] for Ord[List[T]] { - def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { + 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 = x.compareTo(y) + val fst = ord.compare(x, y) if (fst != 0) fst else xs1.compareTo(ys1) } } ``` -Inferred instance definitions can be seen as shorthands for what is currently expressed with implicit object and method definitions. -For example, the definition of the inferred instance `IntOrd` above defines an implicit value of type `Ord[Int]`. It is hence equivalent -to the following implicit object definition: -```scala -implicit object IntOrd extends Ord[Int] { - def (x: Int) compareTo (y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 -} -``` -The definition of the inferred instance `ListOrd` defines an ordering for `List[T]` provided there is an ordering for type `T`. With existing -implicits, this could be expressed as a pair of a class and an implicit method: -```scala -class ListOrd[T: Ord] extends Ord[List[T]] { - def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = x.compareTo(y) - if (fst != 0) fst else xs1.compareTo(ys1) - } -} -implicit def ListOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] -``` -## Inferred Instances for Extension Methods - -Inferred instances can also be defined without a `for` clause. A typical application is to use an inferred instance to package some extension methods. Examples: +This code defines a trait `Ord` and two inferred instance definitions. `IntOrd` defines +an inferred instance for the type `Ord[Int]` whereas `ListOrd` defines inferred +instances of `Ord[List[T]]` for any type `T` that comes with an inferred `Ord` instance itself. +The `given` clause in `ListOrd` defines an [implicit parameter](./implicit-params.html). +Implicit parameters are further explained in the next section. -```scala -inferred StringOps { - def (xs: Seq[String]) longestStrings: Seq[String] = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } -} - -inferred ListOps { - def (xs: List[T]) second[T] = xs.tail.head -} -``` ## Anonymous Inferred Instances -The name of an inferred instance can be left out. Examples: +The name of an inferred instance can be left out. So the inferred instance definitions +of the last section can also be expressed like this: ```scala inferred for Ord[Int] { ... } inferred [T: Ord] for Ord[List[T]] { ... } -inferred { - def (xs: List[T]) second[T] = xs.tail.head -} -``` -If the name of an inferred instance is missing, the compiler will synthesize a name from -the type in the of clause, or, if that is missing, from the first defined -extension method. - -**Aside: ** Why anonymous inferred instances? - - - It avoids clutter, relieving the programmer from having to invent names that are never referred to. - Usually the invented names are either meaning less (e.g. `ev1`), or they just rephrase the implemented type. - - It gives a systematic foundation for synthesized inferred instance definitions, such as those coming from a `derives` clause. - - It achieves a uniform principle that the name of an implicit is always optional, no matter - whether the implicit is an inferred instance definition or an implicit parameter. +If the name of an instance is missing, the compiler will synthesize a name from +the type(s) in the `for` clause. -## Conditional Instances - -An inferred instance definition can depend on another inferred instance being defined. Example: -```scala -trait Conversion[-From, +To] { - def apply(x: From): To -} - -inferred [S, T] given (c: Conversion[S, T]) for Conversion[List[S], List[T]] { - def convert(x: List[From]): List[To] = x.map(c.apply) -} -``` -This defines an implicit conversion from `List[S]` to `List[T]` provided there is an implicit conversion from `S` to `T`. -The `given` clause defines required instances. The `Conversion[List[From], List[To]]` instance above -is defined only if a `Conversion[From, To]` instance exists. - -Context bounds in inferred instance definitions also translate to implicit parameters, -and therefore they can be represented alternatively as `given` clauses. For example, -here is an equivalent definition of the `ListOrd` instance: -```scala -inferred ListOrd[T] given (ord: Ord[T]) for List[Ord[T]] { ... } -``` -The name of a parameter in a `given` clause can also be left out, as shown in the following variant of `ListOrd`: -```scala -inferred ListOrd[T] given Ord[T] for List[Ord[T]] { ... } -``` -As usual one can then infer to implicit parameter only indirectly, by passing it as implicit argument to another function. - -## Typeclass Instances - -Instance definitions allow a concise and natural expression of typeclasses. -Here are some examples of standard typeclass instances: - -Semigroups and monoids: - -```scala -trait SemiGroup[T] { - def (x: T) combine (y: T): T -} -trait Monoid[T] extends SemiGroup[T] { - def unit: T -} -object Monoid { - def apply[T] = implicitly[Monoid[T]] -} - -inferred for Monoid[String] { - def (x: String) combine (y: String): String = x.concat(y) - def unit: String = "" -} - -def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(Monoid[T].unit)(_.combine(_)) -``` -Functors and monads: -```scala -trait Functor[F[_]] { - def (x: F[A]) map[A, B] (f: A => B): F[B] -} - -trait Monad[F[_]] extends Functor[F] { - def (x: F[A]) flatMap[A, B] (f: A => F[B]): F[B] - def (x: F[A]) map[A, B] (f: A => B) = x.flatMap(f `andThen` pure) - - def pure[A](x: A): F[A] -} - -inferred ListMonad for Monad[List] { - def (xs: List[A]) flatMap[A, B] (f: A => List[B]): List[B] = - xs.flatMap(f) - def pure[A](x: A): List[A] = - List(x) -} - -inferred ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { - def (r: Ctx => A) flatMap[A, B] (f: A => Ctx => B): Ctx => B = - ctx => f(r(ctx))(ctx) - def pure[A](x: A): Ctx => A = - ctx => x -} -``` ## Inferred Alias Instances An inferred alias instance creates an inferred instance that is equal to @@ -199,8 +72,8 @@ Here is the new syntax of inferred instance definitions, seen as a delta from th TmplDef ::= ... | ‘inferred’ InstanceDef InstanceDef ::= [id] InstanceParams InstanceBody -InstanceParams ::= [DefTypeParamClause] {InstParamClause} -InstParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) +InstanceParams ::= [DefTypeParamClause] {InferParamClause} +InferParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) InstanceBody ::= [‘for’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] | ‘for’ Type ‘=’ Expr ContextTypes ::= RefinedType {‘,’ RefinedType} diff --git a/docs/docs/reference/instances/relationship-implicits.md b/docs/docs/reference/instances/relationship-implicits.md new file mode 100644 index 000000000000..4c8852fee934 --- /dev/null +++ b/docs/docs/reference/instances/relationship-implicits.md @@ -0,0 +1,152 @@ +--- +layout: doc-page +title: Relationship with Scala 2 Implicits" +--- + +Many, but not all, of the new implicit features can be mapped to Scala-2 implicits. This page gives a rundown on the relationships between new and old features. + +# Simulating Dotty's New Implicits in Scala 2 + +### Inferred Instance Definitions + +Inferred instance definitions can be mapped to combinations of implicit objects, classes and implicit methods. +Instance definitions without parameters are mapped to implicit objects. E.g., +```scala + inferred IntOrd for Ord[Int] { ... } +``` +maps to +```scala + implicit object IntOrd extends Ord[Int] { ... } +``` +Parameterized instance definitions are mapped to combinations of classes and implicit methods. E.g., +```scala + inferred ListOrd[T] given (ord: Ord[T]) for 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] +``` +Inferred alias instances map to implicit methods. E.g., +```scala + inferred ctx for ExecutionContext = ... +``` +maps to +```scala + final implicit def ctx: ExecutionContext = ... +``` +### Anonymous Inferred Instances + +Anonymous 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` instances above were left out, the following names would be synthesized instead: +```scala + inferred Ord_Int_instance for Ord[Int] { ... } + inferred Ord_List_instance[T] for Ord[List[T]] { ... } +``` +The synthesized type names are formed from + + - 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 + - the suffix `_instance`. + +Anonymous 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 instance +```scala + inferred { + def (xs: List[T]) second[T] = ... + } +``` +gets the synthesized name `second_of_List_T_instance`. + +### Inferable Parameters + +The new inferable parameter syntax with `given` corresponds largely to Scala-2's implicit parameters. 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 inferable parameters _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 `infer` method corresponds to `implicitly` in Scala 2. + +### Context Bounds + +Context bounds are the same in both language versions. They expand to the respective forms of implicit parameters. + +Note: To make migration simpler, 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. + +### 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 implicit 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 in Dotty + +### Implicit Conversions + +Implicit conversion methods in Scala 2 can be expressed as 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 + inferred stringToToken for 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 conversion instance. + +### Implicit Values + +Implicit `val` definitions in Scala 2 can be expressed in Dotty using a regular `val` definition and an instance alias. 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 + inferred for 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 instance alias. E.g., Scala 2's +``` + implicit def symDeco: SymDeco +``` +can be expressed in Dotty as +``` + def symDeco: SymDeco + inferred for SymDeco = symDeco +``` diff --git a/docs/docs/reference/instances/typeclasses.md b/docs/docs/reference/instances/typeclasses.md new file mode 100644 index 000000000000..5bbbbfd067b8 --- /dev/null +++ b/docs/docs/reference/instances/typeclasses.md @@ -0,0 +1,64 @@ +--- +layout: doc-page +title: "Implementing Typeclasses" +--- + +Inferred instance definitions, extension methods and context bounds +allow a concise and natural expression of _typeclasses_. Typeclasses are just traits +with canonical implementations defined by inferred instance definitions. Here are some examples of standard typeclasses: + +### Semigroups and monoids: + +```scala +trait SemiGroup[T] { + def (x: T) combine (y: T): T +} +trait Monoid[T] extends SemiGroup[T] { + def unit: T +} +object Monoid { + def apply[T] = infer[Monoid[T]] +} + +inferred for Monoid[String] { + def (x: String) combine (y: String): String = x.concat(y) + def unit: String = "" +} + +inferred for Monoid[Int] { + def (x: Int) combine (y: Int): Int = x + y + def unit: String = 0 +} + +def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(Monoid[T].unit)(_.combine(_)) +``` + +### Functors and monads: + +```scala +trait Functor[F[_]] { + def (x: F[A]) map [A, B] (f: A => B): F[B] +} + +trait Monad[F[_]] extends Functor[F] { + def (x: F[A]) flatMap [A, B] (f: A => F[B]): F[B] + def (x: F[A]) map [A, B] (f: A => B) = x.flatMap(f `andThen` pure) + + def pure[A](x: A): F[A] +} + +inferred ListMonad for Monad[List] { + def (xs: List[A]) flatMap [A, B] (f: A => List[B]): List[B] = + xs.flatMap(f) + def pure[A](x: A): List[A] = + List(x) +} + +inferred ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { + def (r: Ctx => A) flatMap [A, B] (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/other-new-features/extension-methods.md b/docs/docs/reference/other-new-features/extension-methods.md index 3545a99144d2..8f8c13c51978 100644 --- a/docs/docs/reference/other-new-features/extension-methods.md +++ b/docs/docs/reference/other-new-features/extension-methods.md @@ -48,7 +48,7 @@ trait StringSeqOps { } } ``` -We can make the extension method available by defining an implicit instance of `StringSeqOps`, like this: +We can make the extension method available by defining an inferred instance of `StringSeqOps`, like this: ```scala implicit object ops1 extends StringSeqOps ``` @@ -123,50 +123,6 @@ def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y) As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the preceding parameter clause. -### A Larger Example - -As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extensions to provide a zero-overhead abstraction. - -```scala -object PostConditions { - opaque type WrappedResult[T] = T - - private object WrappedResult { - def wrap[T](x: T): WrappedResult[T] = x - def unwrap[T](x: WrappedResult[T]): T = x - } - - def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) - - implicit object Ensuring { - def (x: T) ensuring [T](condition: implicit WrappedResult[T] => Boolean): T = { - implicit val wrapped = WrappedResult.wrap(x) - assert(condition) - x - } - } -} - -object Test { - import PostConditions._ - val s = List(1, 2, 3).sum.ensuring(result == 6) -} -``` -**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean` -as the type of the condition of `ensuring`. An argument condition to `ensuring` such as -`(result == 6)` will therefore have an implicit value 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 implicits 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: - - { val result = List(1, 2, 3).sum - assert(result == 6) - result - } - -**A note on formatting** Having a parameter section in front of the defined -method name makes it visually harder to discern what is being defined. To address -that problem, it is recommended that the name of an extension method is -preceded by a space and is also followed by a space if there are more parameters -to come. ### Extension Methods and Type Classes @@ -181,7 +137,7 @@ The rules for expanding extension methods make sure that they work seamlessly wi } object Monoid { // An instance declaration: - implicit object StringMonoid extends Monoid[String] { + inferred StringMonoid for Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -189,15 +145,15 @@ The rules for expanding extension methods make sure that they work seamlessly wi // Abstracting over a typeclass with a context bound: def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) + xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) ``` In the last line, the call to `_.combine(_)` expands to `(x1, x2) => x1.combine(x2)`, -which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the implicit +which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the inferred evidence parameter summoned by the context bound `[T: Monoid]`. This works since extension methods apply everywhere their enclosing object is available as an implicit. -To avoid having to write `implicitly[Monoid[T]].unit` to access the `unit` method in `Monoid[T]`, +To avoid having to write `summon[Monoid[T]].unit` to access the `unit` method in `Monoid[T]`, we can make `unit` itself an extension method on the `Monoid` _companion object_, as shown below: @@ -206,14 +162,14 @@ as shown below: def (self: Monoid.type) unit: T } object Monoid { - implicit object StringMonoid extends Monoid[String] { + inferred StringMonoid for Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def (self: Monoid.type) unit: String = "" } } ``` -This allows us to write `Monoid.unit` instead of `implicitly[Monoid[T]].unit`, +This allows us to write `Monoid.unit` instead of `summon[Monoid[T]].unit`, letting the expected type distinguish which instance we want to use: ```scala @@ -221,77 +177,10 @@ letting the expected type distinguish which instance we want to use: xs.foldLeft(Monoid.unit)(_.combine(_)) ``` - -### Generic Extension Classes - -As another example, consider implementations of an `Ord` type class with a `minimum` value: -```scala - trait Ord[T] { - def (x: T) compareTo (y: T): Int - def (x: T) < (y: T) = x.compareTo(y) < 0 - def (x: T) > (y: T) = x.compareTo(y) > 0 - val minimum: T - } - - implicit object IntOrd extends Ord[Int] { - def (x: Int) compareTo (y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 - val minimum = Int.MinValue - } - - class ListOrd[T: Ord] extends Ord[List[T]] { - def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = x.compareTo(y) - if (fst != 0) fst else xs1.compareTo(ys1) - } - val minimum: List[T] = Nil - } - implicit def ListOrd[T: Ord]: ListOrd[T] = new ListOrd[T] - - - def max[T: Ord](x: T, y: T): T = if (x < y) y else x - - def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) -``` - -### Higher Kinds - -Extension methods generalize to higher-kinded types without requiring special provisions. Example: - -```scala - trait Functor[F[_]] { - def (x: F[A]) map [A, B](f: A => B): F[B] - } - - trait Monad[F[_]] extends Functor[F] { - def (x: F[A]) flatMap [A, B](f: A => F[B]): F[B] - def (x: F[A]) map [A, B](f: A => B) = x.flatMap(f `andThen` pure) - - def pure[A](x: A): F[A] - } - - implicit object ListMonad extends Monad[List] { - def (xs: List[A]) flatMap [A, B](f: A => List[B]): List[B] = - xs.flatMap(f) - def pure[A](x: A): List[A] = - List(x) - } - - implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { - def (r: Ctx => A) flatMap [A, B](f: A => Ctx => B): Ctx => B = - ctx => f(r(ctx))(ctx) - def pure[A](x: A): Ctx => A = - ctx => x - } -``` ### Syntax The required syntax extension just adds one clause for extension methods relative -to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). +to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.html). ``` DefSig ::= ... | ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 895beb889a1c..01bebbf630b9 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -39,10 +39,29 @@ sidebar: url: docs/reference/enums/adts.html - title: Translation url: docs/reference/enums/desugarEnums.html - - title: Other New Features - subsection: + - title: Contextual Abstractions + - title: Inferred Instances + url: docs/reference/instances/instance-defs.html + - title: Inferable Parameters + url: docs/reference/instances/context-params.html + - title: Context Bounds + url: docs/reference/instances/context-bounds.html + - title: Extension Methods + url: docs/reference/instances/extension-methods.html + - title: Implementing Typeclasses + url: docs/reference/instances/typeclasses.html - title: Typeclass Derivation url: docs/reference/derivation.html + - title: Implicit Function Types + url: docs/reference/instances/implicit-function-types.html + - title: Implicit Conversions + url: docs/reference/instances/implicit-conversions.html + - title: Implicit By-Name Parameters + url: docs/reference/instances/implicit-by-name-parameters.html + - title: Relationship with Scala 2 Implicits + url: docs/reference/instances/relationship-implicits.html + - title: Other New Features + subsection: - title: Multiversal Equality url: docs/reference/other-new-features/multiversal-equality.html - title: Trait Parameters @@ -55,10 +74,6 @@ sidebar: url: docs/reference/other-new-features/tasty-reflect.html - title: Opaque Type Aliases url: docs/reference/other-new-features/opaques.html - - title: Extension Methods - url: docs/reference/other-new-features/extension-methods.html - - title: By-Name Implicits - url: docs/reference/other-new-features/implicit-by-name-parameters.html - title: Auto Parameter Tupling url: docs/reference/other-new-features/auto-parameter-tupling.html - title: Named Type Arguments @@ -67,18 +82,6 @@ sidebar: url: docs/reference/other-new-features/erased-terms.html - title: Kind Polymorphism url: docs/reference/other-new-features/kind-polymorphism.html - - title: New Implicits - subsection: - - title: Instance Definitions - url: docs/reference/instances/instance-defs.html - - title: Context Parameters - url: docs/reference/instances/context-params.html - - title: Implicit Function Types - url: docs/reference/instances/implicit-function-types.html - - title: Implicit Conversions - url: docs/reference/instances/implicit-conversions.html - - title: ReplacedImplicits - url: docs/reference/instances/replacing-implicits.html - title: Other Changed Features subsection: - title: Volatile Lazy Vals diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 0bdd5719c39d..be0c1af3ad94 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -37,5 +37,5 @@ object DottyPredef { */ @forceInline def valueOf[T](implicit vt: ValueOf[T]): T = vt.value - @forceInline def summon[T](implicit x: T) = x + @forceInline def infer[T](implicit x: T) = x } diff --git a/tests/pos/implicit-conversion.scala b/tests/pos/implicit-conversion.scala new file mode 100644 index 000000000000..d6bc9f6be86c --- /dev/null +++ b/tests/pos/implicit-conversion.scala @@ -0,0 +1,6 @@ +object Test { + // a problematic implicit conversion, should we flag it? + inferred for Conversion[String, Int] { + def apply(x: String): Int = Integer.parseInt(toString) + } +} \ No newline at end of file diff --git a/tests/pos/reference/instances.scala b/tests/pos/reference/instances.scala index 59f6cbd21ff9..01d5f577e245 100644 --- a/tests/pos/reference/instances.scala +++ b/tests/pos/reference/instances.scala @@ -120,20 +120,20 @@ object Instances extends Common { def f() = { locally { inferred for Context = this.ctx - println(summon[Context].value) + println(infer[Context].value) } locally { lazy val ctx1 = this.ctx inferred for Context = ctx1 - println(summon[Context].value) + println(infer[Context].value) } locally { inferred d[T] for D[T] - println(summon[D[Int]]) + println(infer[D[Int]]) } locally { inferred given Context for D[Int] - println(summon[D[Int]]) + println(infer[D[Int]]) } } } @@ -203,7 +203,7 @@ object AnonymousInstances extends Common { } def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) + xs.foldLeft(infer[Monoid[T]].unit)(_.combine(_)) } object Implicits extends Common { From 1e47e5cc1fc2b140a7d0c6326d2a2c74c12cdbe0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 31 Jan 2019 10:29:45 +0100 Subject: [PATCH 09/16] Rename "implicit function type" to "query type" --- .../changed-features/eta-expansion-spec.md | 9 ++-- .../reference/instances/context-params.md | 13 ++--- .../instances/implicit-function-types-spec.md | 54 +++++++++---------- .../instances/implicit-function-types.md | 42 ++++++++------- tests/run/tagless.scala | 2 +- 5 files changed, 58 insertions(+), 62 deletions(-) diff --git a/docs/docs/reference/changed-features/eta-expansion-spec.md b/docs/docs/reference/changed-features/eta-expansion-spec.md index c3d1a8001150..f5c83e140d9e 100644 --- a/docs/docs/reference/changed-features/eta-expansion-spec.md +++ b/docs/docs/reference/changed-features/eta-expansion-spec.md @@ -48,14 +48,13 @@ implicit val bla: Double = 1.0 val bar = foo // val bar: Int => Float = ... ``` -## Automatic Eta-Expansion and implicit function types +## Automatic Eta-Expansion and query types -Methods with implicit parameter lists can be assigned to a value with an implicit function type -only by using the expected type explicitly. +A method with inferable parameters can be expanded to a value of query type by writing the expected query type explicitly. ```scala -def foo(x: Int)(implicit p: Double): Float = ??? -val bar: implicit Double => Float = foo(3) // val bar: implicit Double => Float = ... +def foo(x: Int) given (p: Double): Float = ??? +val bar: given Double => Float = foo(3) // val bar: given Double => Float = ... ``` ## Rules diff --git a/docs/docs/reference/instances/context-params.md b/docs/docs/reference/instances/context-params.md index dccddcc464e6..f351fa1e0b01 100644 --- a/docs/docs/reference/instances/context-params.md +++ b/docs/docs/reference/instances/context-params.md @@ -1,6 +1,6 @@ --- layout: doc-page -title: "Inferable Parameters and Arguments" +title: "Inferable Paramters" --- Functional programming tends to express most dependencies as simple functions parameterization. @@ -25,7 +25,7 @@ max(2, 3) ``` This is equally valid, and is completed by the compiler to the previous application. -## Anonymous Inferred Parameters +## Anonymous Inferable Parameters In many situations, the name of an inferable parameter of a method need not be mentioned explicitly at all, since it is only used in synthesized arguments for @@ -54,7 +54,7 @@ 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)`. -But usually, explicit arguments for inferable parameters are be left out. For instance, +But usually, explicit arguments for inferable parameters are left out. For instance, given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:) ```scala minimum(xs) @@ -85,10 +85,10 @@ f("abc") given ctx (f given global)("abc") given ctx ``` -## Summmoning an Inferred Instance +## Querying For Inferred Instances -A method `infer` in `Predef` creates an instance value for a given type. For example, -the instance value for `Ord[List[Int]]` is generated by +A method `infer` in `Predef` creates an inferred instance of a given type. For example, +the inferred instance of `Ord[List[Int]]` is generated by ``` infer[Ord[List[Int]]] ``` @@ -96,6 +96,7 @@ The `infer` method is simply defined as the identity function with an inferable ```scala def infer[T] given (x: T) = x ``` +Functions like `infer` that have only inferable parameters are also called inference _queries_. ## Syntax diff --git a/docs/docs/reference/instances/implicit-function-types-spec.md b/docs/docs/reference/instances/implicit-function-types-spec.md index f70a128baa73..001e5637e301 100644 --- a/docs/docs/reference/instances/implicit-function-types-spec.md +++ b/docs/docs/reference/instances/implicit-function-types-spec.md @@ -1,6 +1,6 @@ --- layout: doc-page -title: "Implicit Function Types - More Details" +title: "Query Types - More Details" --- Initial implementation in (#1775)[https://github.com/lampepfl/dotty/pull/1775]. @@ -12,66 +12,61 @@ Initial implementation in (#1775)[https://github.com/lampepfl/dotty/pull/1775]. Expr ::= ... | `given' FunParams `=>' Expr -Implicit function types associate to the right, e.g. +Query 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 +Query types are shorthands for class types that define `apply` +methods with inferable 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`: +`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 with (x1: T1 , ... , xN: TN): R + def apply given (x1: T1 , ... , xN: TN): R } ``` -Implicit function types erase to normal function types, so these classes are +Query types erase to normal function types, so these classes are generated on the fly for typechecking, but not realized in actual code. -Anonymous implicit function values `given (x1: T1, ..., xn: Tn) => e` map -implicit parameters `xi` of types `Ti` to a result given by expression `e`. -The scope of each implicit parameter `xi` is `e`. Implicit parameters must -have pairwise distinct names. +Query literals `given (x1: T1, ..., xn: Tn) => e` map +inferable parameters `xi` of types `Ti` to a result given by expression `e`. +The scope of each implicit parameter `xi` is `e`. The parameters must have pairwise distinct names. -If the expected type of the anonymous implicit function is of the form +If the expected type of the query 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 anonymous implicit function is -some other type, all implicit parameter types must be explicitly given, and -the expected type of `e` is undefined. The type of the anonymous implicit -function is `scala.ImplicitFunctionN[S1, ...,Sn, T]`, where `T` is the widened += Si` is assumed. If the expected type of the query literal is +some other type, all inferable parameter types must be explicitly given, and the expected type of `e` is undefined. The type of the query 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 inferable parameters `xi`. -The anonymous implicit function is evaluated as the instance creation +The query literal is evaluated as the instance creation expression: new scala.ImplicitFunctionN[T1, ..., Tn, T] { - def apply with (x1: T1, ..., xn: Tn): T = e + def apply given (x1: T1, ..., xn: Tn): T = e } -In the case of a single untyped implicit parameter, `given (x) => e` can be +In the case of a single untyped parameter, `given (x) => e` can be abbreviated to `given x => e`. -A implicit parameter may also be a wildcard represented by an underscore `_`. In +An inferable 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 the Scala 2.12 is subsumed by implicit function types and should +functions) of the Scala 2.12 is subsumed by query types and should be removed. -Anonymous implicit functions `given (x1: T1, ..., xn: Tn) => e` are -automatically inserted around any expression `e` whose expected type is +Query 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 function literal. This is analogous to the automatic +itself a query literal. This is analogous to the automatic insertion of `scala.Function0` around expressions in by-name argument position. -Implicit functions generalize to `N > 22` in the same way that functions do, -see [the corresponding +Query types generalize to `N > 22` in the same way that function types do, see [the corresponding documentation](https://dotty.epfl.ch/docs/reference/dropped-features/limit22.html). ## Examples @@ -84,5 +79,4 @@ Gist](https://gist.github.com/OlivierBlanvillain/234d3927fe9e9c6fba074b53a7bd9 ### Type Checking -After desugaring no additional typing rules are required for implicit function -types. +After desugaring no additional typing rules are required for query types. diff --git a/docs/docs/reference/instances/implicit-function-types.md b/docs/docs/reference/instances/implicit-function-types.md index 2f0631fc4ce3..5796dde3d4f0 100644 --- a/docs/docs/reference/instances/implicit-function-types.md +++ b/docs/docs/reference/instances/implicit-function-types.md @@ -1,14 +1,15 @@ --- layout: doc-page -title: "Implicit Function Types and Values" +title: "First Class Queries" --- -An implicit function type describes functions with inferred parameters. Example: +In the context of inference, _queries_ are functions with inferable parameters. +_Query types_ are the types of first-class queries. Example: ```scala type Contextual[T] = given Context => T ``` -A value of implicit function type is applied to inferred arguments, in -the same way a method with inferred parameters is applied. For instance: +A value of query type is applied to inferred arguments, in +the same way a method with inferable parameters is applied. For instance: ```scala inferred ctx for Context = ... @@ -17,18 +18,20 @@ the same way a method with inferred parameters is applied. For instance: f(2) given ctx // 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 value, `E` is converted to an inferred function value -by rewriting to +Conversely, if the expected type of an expression `E` is a query +type `given (T_1, ..., T_n) => U` and `E` is not already a +query literal, `E` is converted to a query literal by rewriting to ```scala given (x_1: T1, ..., x_n: Tn) => E ``` -where the names `x_1`, ..., `x_n` are arbitrary. Inferred function values are written -with a `given` prefix. They differ from normal function values in two ways: +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 inferred instances in `E`. - 1. Their parameters are inferred parameters - 2. Their types are implicit function types. +Like query types, query literals are written with a `given` prefix. They differ from normal function literals in two ways: + + 1. Their parameters are inferable. + 2. Their types are query types. For example, continuing with the previous definitions, ```scala @@ -42,7 +45,7 @@ For example, continuing with the previous definitions, ``` ### Example: Builder Pattern -Implicit function types have considerable expressive power. For +Query 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 @@ -75,17 +78,17 @@ addition of elements via `add`: case class Cell(elem: String) ``` Then, the `table`, `row` and `cell` constructor methods can be defined -in terms of implicit function types to avoid the plumbing boilerplate +in terms of query types to avoid the plumbing boilerplate that would otherwise be necessary. ```scala def table(init: given Table => Unit) = { - instance t of Table + inferred t for Table init t } def row(init: given Row => Unit) given (t: Table) = { - instance r of Row + inferred r for Row init t.add(r) } @@ -108,7 +111,7 @@ With that setup, the table construction code above compiles and expands to: ``` ### Example: Postconditions -As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extensions to provide a zero-overhead abstraction. +As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, query types, and extension methods to provide a zero-overhead abstraction. ```scala object PostConditions { @@ -133,10 +136,9 @@ object Test { val s = List(1, 2, 3).sum.ensuring(result == 6) } ``` -**Explanations**: We use an implicit function type `given WrappedResult[T] => Boolean` +**Explanations**: We use a query type `given WrappedResult[T] => Boolean` as the type of the condition of `ensuring`. An argument to `ensuring` such as -`(result == 6)` will therefore have an implicit value 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 inferred instances in scope (this is good practice in all cases where inferred 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: +`(result == 6)` will therefore have an inferred 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 inferred instances in scope (this is good practice in all cases where inferred 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: { val result = List(1, 2, 3).sum assert(result == 6) diff --git a/tests/run/tagless.scala b/tests/run/tagless.scala index 99f0440cc877..d764f696577b 100644 --- a/tests/run/tagless.scala +++ b/tests/run/tagless.scala @@ -1,7 +1,7 @@ // A rewrite of Olivier Blanvillain's [adaptation](https://gist.github.com/OlivierBlanvillain/48bb5c66dbb0557da50465809564ee80) // of Oleg Kislyov's [lecture notes](http://okmij.org/ftp/tagless-final/course/lecture.pdf) // on tagless final interpreters. -// Main win: Replace Either by an "algebraic effect" using an implicit function type. +// Main win: Replace Either by an "algebraic effect" using a query type. object Test extends App { // Explicit ADT From e792c8802a210a020a20add22f115a8941407ec4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 31 Jan 2019 11:03:10 +0100 Subject: [PATCH 10/16] Cleanups --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 4 +++- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 88ce23acdddc..dd2f2facbf4a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2816,9 +2816,11 @@ object Parsers { setLastStatOffset() if (in.token == IMPORT) stats ++= importClause() + else if (in.token == GIVEN) + stats += implicitClosure(in.offset, Location.InBlock, modifiers(closureMods)) else if (isExprIntro) stats += expr(Location.InBlock) - else if (isDefIntro(localModifierTokens) || in.token == GIVEN) // !!!! + else if (isDefIntro(localModifierTokens)) if (in.token == IMPLICIT || in.token == ERASED || in.token == GIVEN) { val start = in.offset var imods = modifiers(closureMods) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 8d977fea1d1c..8c754febb208 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -141,7 +141,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { argStr ~ " => " ~ argText(args.last) } - def toTextDependentFunction(appType: MethodType): Text = // !!!! + def toTextDependentFunction(appType: MethodType): Text = + (keywordText("erased ") provided appType.isErasedMethod) ~ (keywordText("given ") provided appType.isImplicitMethod) ~ "(" ~ paramsText(appType) ~ ") => " ~ toText(appType.resultType) From 3bc05474b06a18ae6bd40980d447de5eed5f59f2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 31 Jan 2019 11:39:48 +0100 Subject: [PATCH 11/16] Renaming "instance of" -> "implied for" --- .../src/dotty/tools/dotc/core/StdNames.scala | 1 - .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- .../dotty/tools/dotc/parsing/Scanners.scala | 4 +- .../src/dotty/tools/dotc/parsing/Tokens.scala | 4 +- .../src/dotty/tools/dotc/typer/Typer.scala | 10 +- .../context-bounds.md | 4 +- docs/docs/reference/contextual/conversions.md | 78 +++++++ .../{instances => contextual}/derivation.md | 32 +-- .../extension-methods.md | 15 +- .../implicit-conversions.md | 12 +- .../inferable-by-name-parameters.md} | 24 +-- .../inferable-params.md} | 50 +++-- .../reference/contextual/instance-defs.md | 79 ++++++++ .../query-types-spec.md} | 0 .../query-types.md} | 20 +- .../relationship-implicits.md | 31 +-- .../replacing-implicits.md | 0 .../{instances => contextual}/typeclasses.md | 12 +- .../instances/discussion/context-params.md | 121 ----------- .../instances/discussion/discussion.md | 52 ----- .../instances/discussion/instance-defs.md | 186 ----------------- .../instances/discussion/motivation.md | 48 ----- .../discussion/replacing-implicits.md | 111 ---------- .../docs/reference/instances/instance-defs.md | 82 -------- .../other-new-features/extension-methods.md | 191 ------------------ docs/sidebar.yml | 26 +-- tests/new/test.scala | 4 +- tests/pos/givenIn.scala | 2 +- tests/pos/implicit-conversion.scala | 2 +- tests/pos/reference/instances.scala | 46 ++--- tests/run/instances-anonymous.scala | 20 +- tests/run/instances.scala | 22 +- tests/run/tagless.scala | 20 +- 33 files changed, 341 insertions(+), 972 deletions(-) rename docs/docs/reference/{instances => contextual}/context-bounds.md (61%) create mode 100644 docs/docs/reference/contextual/conversions.md rename docs/docs/reference/{instances => contextual}/derivation.md (91%) rename docs/docs/reference/{instances => contextual}/extension-methods.md (84%) rename docs/docs/reference/{instances => contextual}/implicit-conversions.md (88%) rename docs/docs/reference/{instances/implicit-by-name-parameters.md => contextual/inferable-by-name-parameters.md} (53%) rename docs/docs/reference/{instances/context-params.md => contextual/inferable-params.md} (71%) create mode 100644 docs/docs/reference/contextual/instance-defs.md rename docs/docs/reference/{instances/implicit-function-types-spec.md => contextual/query-types-spec.md} (100%) rename docs/docs/reference/{instances/implicit-function-types.md => contextual/query-types.md} (83%) rename docs/docs/reference/{instances => contextual}/relationship-implicits.md (85%) rename docs/docs/reference/{instances => contextual}/replacing-implicits.md (100%) rename docs/docs/reference/{instances => contextual}/typeclasses.md (77%) delete mode 100644 docs/docs/reference/instances/discussion/context-params.md delete mode 100644 docs/docs/reference/instances/discussion/discussion.md delete mode 100644 docs/docs/reference/instances/discussion/instance-defs.md delete mode 100644 docs/docs/reference/instances/discussion/motivation.md delete mode 100644 docs/docs/reference/instances/discussion/replacing-implicits.md delete mode 100644 docs/docs/reference/instances/instance-defs.md delete mode 100644 docs/docs/reference/other-new-features/extension-methods.md diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 339d811f26d0..b0f3db0429aa 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -483,7 +483,6 @@ object StdNames { val notifyAll_ : N = "notifyAll" val notify_ : N = "notify" val null_ : N = "null" - val of: N = "of" val ofDim: N = "ofDim" val opaque: N = "opaque" val ordinal: N = "ordinal" diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index dd2f2facbf4a..1d7e348e4beb 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2542,11 +2542,11 @@ object Parsers { */ def instanceDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) { var mods1 = addMod(mods, instanceMod) - val name = if (isIdent && !isIdent(nme.of)) ident() else EmptyTermName + val name = if (isIdent) ident() else EmptyTermName val tparams = typeParamClauseOpt(ParamOwner.Def) val vparamss = paramClauses(ofInstance = true) val parents = - if (isIdent(nme.of)) { + if (in.token == FOR) { in.nextToken() tokenSeparated(COMMA, constrApp) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 28da40700013..2ea56b46cff3 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -205,11 +205,9 @@ object Scanners { private def handleMigration(keyword: Token): Token = if (!isScala2Mode) keyword - else if ( keyword == ENUM - || keyword == ERASED) treatAsIdent() + else if (scala3keywords.contains(keyword)) treatAsIdent() else keyword - private def treatAsIdent() = { testScala2Mode(i"$name is now a keyword, write `$name` instead of $name to keep it as an identifier") patch(source, Span(offset), "`") diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index a11dbec4bea8..8ad818487dff 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -178,7 +178,7 @@ object Tokens extends TokensCommon { final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate final val ENUM = 62; enter(ENUM, "enum") final val ERASED = 63; enter(ERASED, "erased") - final val INSTANCE = 64; enter(INSTANCE, "instance") + final val INSTANCE = 64; enter(INSTANCE, "implied") final val GIVEN = 65; enter(GIVEN, "given") /** special symbols */ @@ -251,5 +251,7 @@ object Tokens extends TokensCommon { final val numericLitTokens: TokenSet = BitSet(INTLIT, LONGLIT, FLOATLIT, DOUBLELIT) + final val scala3keywords = BitSet(ENUM, ERASED, GIVEN, INSTANCE) + final val softModifierNames = Set(nme.inline, nme.opaque) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 118518cf8faf..80e280defd64 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2719,11 +2719,11 @@ class Typer extends Namer else err.typeMismatch(tree, pt, failure) if (ctx.mode.is(Mode.ImplicitsEnabled) && tree.typeOpt.isValueType) inferView(tree, pt) match { - case SearchSuccess(inferred: ExtMethodApply, _, _) => - inferred // nothing to check or adapt for extension method applications - case SearchSuccess(inferred, _, _) => - checkImplicitConversionUseOK(inferred.symbol, tree.posd) - readapt(inferred)(ctx.retractMode(Mode.ImplicitsEnabled)) + case SearchSuccess(found: ExtMethodApply, _, _) => + found // nothing to check or adapt for extension method applications + case SearchSuccess(found, _, _) => + checkImplicitConversionUseOK(found.symbol, tree.posd) + readapt(found)(ctx.retractMode(Mode.ImplicitsEnabled)) case failure: SearchFailure => if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) // don't report the failure but return the tree unchanged. This diff --git a/docs/docs/reference/instances/context-bounds.md b/docs/docs/reference/contextual/context-bounds.md similarity index 61% rename from docs/docs/reference/instances/context-bounds.md rename to docs/docs/reference/contextual/context-bounds.md index 195a766e6410..8a79cf349eed 100644 --- a/docs/docs/reference/instances/context-bounds.md +++ b/docs/docs/reference/contextual/context-bounds.md @@ -5,11 +5,11 @@ title: "Context Bounds" ## Context Bounds -A context bound is a shorthand for expressing a common pattern of implicit parameters. For example, the `maximum` function of the last section could also have been written like this: +A context bound is a shorthand for expressing a common pattern of an inferable 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 `: C` on a type parameter `T` of a method or class indicates an inferred parameter `given C[T]`. The inferred 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 an inferred parameter `given Ord[T]`. The inferred parameter(s) generated from context bounds come last in the definition of the containing method or class. E.g., ``` def f[T: C1 : C2, U: C3](x: T) given (y: U, z: V): R ``` diff --git a/docs/docs/reference/contextual/conversions.md b/docs/docs/reference/contextual/conversions.md new file mode 100644 index 000000000000..8bf9aeacb91b --- /dev/null +++ b/docs/docs/reference/contextual/conversions.md @@ -0,0 +1,78 @@ +--- +layout: doc-page +title: "Implied Conversions" +--- + +Inferable conversions are defined by implied 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 implied conversion from `String` to `Token`: +```scala +implied for Conversion[String, Token] { + def apply(str: String): Token = new KeyWord(str) +} +``` +An implied 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 in the implied scope for a an instance of +`scala.Conversion` that maps an argument of type `T` to type `S`. In the second and third +case, it looks for an instance of `scala.Conversion` 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 +implied int2Integer for Conversion[Int, java.lang.Integer] { + def apply(x: Int) = new java.lang.Integer(x) +} +``` + +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.from(statusCode) + + implied from for Conversion[String, CompletionArg] { + def apply(s: String) = CompletionArg.Error(s) + } + implied from for Conversion[Future[HttpResponse], CompletionArg] { + def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) + } + implied from for Conversion[Future[StatusCode], CompletionArg] { + def apply(code: Future[StatusCode]) = CompletionArg.Status(code) + } + } + 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/instances/derivation.md b/docs/docs/reference/contextual/derivation.md similarity index 91% rename from docs/docs/reference/instances/derivation.md rename to docs/docs/reference/contextual/derivation.md index 17c2221d64d2..2fe1c6f4097c 100644 --- a/docs/docs/reference/instances/derivation.md +++ b/docs/docs/reference/contextual/derivation.md @@ -10,11 +10,11 @@ enum Tree[T] derives Eq, Ordering, Pickling { case Leaf(elem: T) } ``` -The `derives` clause generates inferred instances of the `Eq`, `Ordering`, and `Pickling` traits in the companion object `Tree`: +The `derives` clause generates implied instances of the `Eq`, `Ordering`, and `Pickling` traits in the companion object `Tree`: ```scala -inferred [T: Eq] for Eq[Tree[T]] = Eq.derived -inferred [T: Ordering] for Ordering[Tree[T]] = Ordering.derived -inferred [T: Pickling] for Pickling[Tree[T]] = Pickling.derived +implied [T: Eq] for Eq[Tree[T]] = Eq.derived +implied [T: Ordering] for Ordering[Tree[T]] = Ordering.derived +implied [T: Pickling] for Pickling[Tree[T]] = Pickling.derived ``` ### Deriving Types @@ -47,7 +47,7 @@ These two conditions ensure that the synthesized derived instances for the trait ```scala def derived[T] with Generic[T] = ... ``` -That is, the `derived` method takes an implicit parameter of type `Generic` that determines the _shape_ of the deriving type `T` and it computes the typeclass implementation according to that shape. A `Generic` instance is generated automatically +That is, the `derived` method takes an inferable parameter of type `Generic` that determines the _shape_ of the deriving type `T` and it computes the typeclass implementation according to that shape. A `Generic` instance is generated automatically for any types that derives a typeclass that needs it. One can also derive `Generic` alone, which means a `Generic` instance is generated without any other type class instances. E.g.: ```scala sealed trait ParseResult[T] derives Generic @@ -99,10 +99,10 @@ is represented as `T *: Unit` since there is no direct syntax for such tuples: ` ### The Generic TypeClass -For every class `C[T_1,...,T_n]` with a `derives` clause, the compiler generates in the companion object of `C` an inferred instance of `Generic[C[T_1,...,T_n]]` that follows +For every class `C[T_1,...,T_n]` with a `derives` clause, the compiler generates in the companion object of `C` an implied instance of `Generic[C[T_1,...,T_n]]` that follows the outline below: ```scala -inferred [T_1, ..., T_n] for Generic[C[T_1,...,T_n]] { +implied [T_1, ..., T_n] for Generic[C[T_1,...,T_n]] { type Shape = ... ... } @@ -120,7 +120,7 @@ would produce: object Result { import scala.compiletime.Shape._ - inferred [T, E] for Generic[Result[T, E]] { + implied [T, E] for Generic[Result[T, E]] { type Shape = Cases[( Case[Ok[T], T *: Unit], Case[Err[E], E *: Unit] @@ -311,7 +311,7 @@ The last, and in a sense most interesting part of the derivation is the comparis error("No `Eq` instance was found for $T") } ``` -`tryEql` is an inline method that takes an element type `T` and two element values of that type as arguments. It is defined using an `inline match` that tries to find an implicit instance of `Eq[T]`. If an instance `ev` is found, it proceeds by comparing the arguments using `ev.eql`. On the other hand, if no instance is found +`tryEql` is an inline method that takes an element type `T` and two element values of that type as arguments. It is defined using an `implicit match` that tries to find an implied instance of `Eq[T]`. If an instance `ev` is found, it proceeds by comparing the arguments using `ev.eql`. On the other hand, if no instance is found this signals a compilation error: the user tried a generic derivation of `Eq` for a class with an element type that does not support an `Eq` instance itself. The error is signaled by calling the `error` method defined in `scala.compiletime`. @@ -320,7 +320,7 @@ calling the `error` method defined in `scala.compiletime`. **Example:** Here is a slightly polished and compacted version of the code that's generated by inline expansion for the derived `Eq` instance of class `Tree`. ```scala -inferred [T] with (elemEq: Eq[T]) for Eq[Tree[T]] { +implied [T] with (elemEq: Eq[T]) for Eq[Tree[T]] { def eql(x: Tree[T], y: Tree[T]): Boolean = { val ev = infer[Generic[Tree[T]]] val mx = ev.reflect(x) @@ -339,21 +339,21 @@ inferred [T] with (elemEq: Eq[T]) for Eq[Tree[T]] { } ``` -One important difference between this approach and Scala-2 typeclass derivation frameworks such as Shapeless or Magnolia is that no automatic attempt is made to generate typeclass instances of elements recursively using the generic derivation framework. There must be an implicit instance of `Eq[T]` (which can of course be produced in turn using `Eq.derived`), or the compilation will fail. The advantage of this more restrictive approach to typeclass derivation is that it avoids uncontrolled transitive typeclass derivation by design. This keeps code sizes smaller, compile times lower, and is generally more predictable. +One important difference between this approach and Scala-2 typeclass derivation frameworks such as Shapeless or Magnolia is that no automatic attempt is made to generate typeclass instances of elements recursively using the generic derivation framework. There must be an implied instance of `Eq[T]` (which can of course be produced in turn using `Eq.derived`), or the compilation will fail. The advantage of this more restrictive approach to typeclass derivation is that it avoids uncontrolled transitive typeclass derivation by design. This keeps code sizes smaller, compile times lower, and is generally more predictable. ### Derived Instances Elsewhere Sometimes one would like to derive a typeclass 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 with the `derived` method of the typeclass as right-hand side. E.g, to implement `Ordering` for `Option`, define: ```scala -inferred [T: Ordering]: Ordering[Option[T]] = Ordering.derived +implied [T: Ordering]: Ordering[Option[T]] = Ordering.derived ``` -Usually, the `Ordering.derived` clause has an implicit parameter of type +Usually, the `Ordering.derived` clause has an inferable parameter of type `Generic[Option[T]]`. Since the `Option` trait has a `derives` clause, -the necessary inferred instance is already present in the companion object of `Option`. -If the ADT in question does not have a `derives` clause, an implicit `Generic` instance +the necessary implied instance is already present in the companion object of `Option`. +If the ADT in question does not have a `derives` clause, an implied `Generic` instance would still be synthesized by the compiler at the point where `derived` is called. -This is similar to the situation with type tags or class tags: If no implicit instance +This is similar to the situation with type tags or class tags: If no implied instance is found, the compiler will synthesize one. ### Syntax diff --git a/docs/docs/reference/instances/extension-methods.md b/docs/docs/reference/contextual/extension-methods.md similarity index 84% rename from docs/docs/reference/instances/extension-methods.md rename to docs/docs/reference/contextual/extension-methods.md index 376b14736ec3..ef225df602af 100644 --- a/docs/docs/reference/instances/extension-methods.md +++ b/docs/docs/reference/contextual/extension-methods.md @@ -36,7 +36,7 @@ When is an extension method applicable? There are two possibilities. - An extension method is applicable if it is visible under a simple name, by being defined or inherited or imported in a scope enclosing the application. - - An extension method is applicable if it is a member of an eligible implicit value at the point of the application. + - An extension method is applicable if it is a member of some implied instance at the point of the application. As an example, consider an extension method `longestStrings` on `String` defined in a trait `StringSeqOps`. @@ -48,16 +48,15 @@ trait StringSeqOps { } } ``` -We can make the extension method available by defining an implicit instance of `StringSeqOps`, like this: +We can make the extension method available by defining an implied instance of `StringSeqOps`, like this: ```scala -instance StringSeqOps1 of StringSeqOps +implied ops1 for StringSeqOps ``` Then ```scala List("here", "is", "a", "list").longestStrings ``` -is legal everywhere `StringSeqOps1` is available as an implicit value. Alternatively, we can define `longestStrings` -as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. +is legal everywhere `ops1` is available as an implied instance. Alternatively, we can define `longestStrings` as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. ```scala object ops2 extends StringSeqOps @@ -70,14 +69,14 @@ Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type ar and where `T` is the expected type. The following two rewritings are tried in order: 1. The selection is rewritten to `m[Ts](e)`. - 2. If the first rewriting does not typecheck with expected type `T`, and there is an implicit value `i` - in either the current scope or in the implicit scope of `T`, and `i` defines an extension + 2. If the first rewriting does not typecheck with expected type `T`, and there is an implied instance `i` + in either the current scope or in the implied scope of `T`, and `i` defines an extension method named `m`, then selection is expanded to `i.m[Ts](e)`. 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. So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided -`circle` has type `Circle` and `CircleOps` is an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). +`circle` has type `Circle` and `CircleOps` is an eligible implied instance (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). ## Instances for Extension Methods diff --git a/docs/docs/reference/instances/implicit-conversions.md b/docs/docs/reference/contextual/implicit-conversions.md similarity index 88% rename from docs/docs/reference/instances/implicit-conversions.md rename to docs/docs/reference/contextual/implicit-conversions.md index 8e742c1a2cae..fe512b53121a 100644 --- a/docs/docs/reference/instances/implicit-conversions.md +++ b/docs/docs/reference/contextual/implicit-conversions.md @@ -3,14 +3,14 @@ layout: doc-page title: "Implicit Conversions" --- -Implicit conversions are defined by inferred instances of the `scala.Conversion` class. +Inferable conversions are defined by implied 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 -inferred for Conversion[String, Token] { +implied for Conversion[String, Token] { def apply(str: String): Token = new KeyWord(str) } ``` @@ -33,7 +33,7 @@ If such an instance `C` is found, 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 -inferred int2Integer for Conversion[Int, java.lang.Integer] { +implied int2Integer for Conversion[Int, java.lang.Integer] { def apply(x: Int) = new java.lang.Integer(x) } ``` @@ -56,13 +56,13 @@ object Completions { // // CompletionArg.from(statusCode) - inferred from for Conversion[String, CompletionArg] { + implied from for Conversion[String, CompletionArg] { def apply(s: String) = CompletionArg.Error(s) } - inferred from for Conversion[Future[HttpResponse], CompletionArg] { + implied from for Conversion[Future[HttpResponse], CompletionArg] { def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) } - inferred from for Conversion[Future[StatusCode], CompletionArg] { + implied from for Conversion[Future[StatusCode], CompletionArg] { def apply(code: Future[StatusCode]) = CompletionArg.Status(code) } } diff --git a/docs/docs/reference/instances/implicit-by-name-parameters.md b/docs/docs/reference/contextual/inferable-by-name-parameters.md similarity index 53% rename from docs/docs/reference/instances/implicit-by-name-parameters.md rename to docs/docs/reference/contextual/inferable-by-name-parameters.md index 815eae1a483b..08c91e6519e1 100644 --- a/docs/docs/reference/instances/implicit-by-name-parameters.md +++ b/docs/docs/reference/contextual/inferable-by-name-parameters.md @@ -1,18 +1,18 @@ --- layout: doc-page -title: "Implicit By-Name Parameters" +title: "Inferable By-Name Parameters" --- -Call-by-name implicit parameters can be used to avoid a divergent implicit expansion. +Call-by-name inferable parameters can be used to avoid a divergent inferred expansion. ```scala trait Codec[T] { def write(x: T): Unit } -inferred intCodec for Codec[Int] = ??? +implied intCodec for Codec[Int] = ??? -inferred optionCodec[T] given (ev: => Codec[T]) for Codec[Option[T]] { +implied optionCodec[T] given (ev: => Codec[T]) for Codec[Option[T]] { def write(xo: Option[T]) = xo match { case Some(x) => ev.write(x) case None => @@ -24,28 +24,28 @@ val s = infer[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` +As is the case for a normal by-name parameter, the argument for the inferable 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 +The synthesized argument for an inferable parameter is backed by a local 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. +The precise steps for constructing an inferable argument for a by-name parameter of type `=> T` are as follows. - 1. Create a new inferred instance of type `T`: + 1. Create a new implied instance of type `T`: ```scala - inferred for T = ??? + implied for T = ??? ``` - 1. This instance 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. This instance is not immediately available as candidate for argument inference (making it immediately available would result in a loop in the synthesized computation). But it becomes available in all nested contexts that look again for an inferred argument to a by-name parameter. - 1. If this implicit search succeeds with expression `E`, and `E` contains references to the inferred instance created previously, replace `E` by + 1. If this search succeeds with expression `E`, and `E` contains references to the implied instance created previously, replace `E` by ```scala - { inferred for T = E; lv } + { implied for T = E; lv } ``` Otherwise, return `E` unchanged. diff --git a/docs/docs/reference/instances/context-params.md b/docs/docs/reference/contextual/inferable-params.md similarity index 71% rename from docs/docs/reference/instances/context-params.md rename to docs/docs/reference/contextual/inferable-params.md index f351fa1e0b01..335e7bf8913e 100644 --- a/docs/docs/reference/instances/context-params.md +++ b/docs/docs/reference/contextual/inferable-params.md @@ -1,6 +1,6 @@ --- layout: doc-page -title: "Inferable Paramters" +title: "Inferable Parameters" --- Functional programming tends to express most dependencies as simple functions parameterization. @@ -9,21 +9,23 @@ call trees where the same value is passed over and over again in long call chain functions. Inferable parameters can help here since they enable the compiler to synthesize repetitive arguments instead of the programmer having to write them explicitly. -For example, given the [instance definitions](./instance-definitions.md) defined previously, +For example, given the [implied instances](./instance-defs.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) < 1) y else x ``` -Here, `ord` is an _inferable parameter_. Inferable parameters are introduced with a `given` clause. Here is an example application of `max`: -``` +Here, `ord` is an _inferable parameter_. Inferable parameters are introduced with a `given` clause. +The `max` method can be applied as follows: +```scala max(2, 3) given IntOrd ``` -The `given IntOrd` part provides the `IntOrd` instance as an argument for the `ord` parameter. But the point of inferable parameters is that this argument can also be left out (and usually is): -``` +The `given IntOrd` part provides the `IntOrd` instance as an argument for the `ord` parameter. But the point of inferable 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) ``` -This is equally valid, and is completed by the compiler to the previous application. ## Anonymous Inferable Parameters @@ -31,7 +33,7 @@ In many situations, the name of an inferable parameter of a method need not be mentioned explicitly at all, since it is only used in synthesized arguments for other inferable 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) ``` @@ -42,7 +44,7 @@ Generally, inferable parameters may be given either as a parameter list `(p_1: T or as a sequence of types, separated by commas. To distinguish the two, a leading `(` always indicates a parameter list. -## Synthesizing Complex Inferred Arguments +## Inferring Complex Arguments Here are two other methods that have an inferable parameter of type `Ord[T]`: ```scala @@ -54,28 +56,24 @@ 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)`. -But usually, explicit arguments for inferable parameters are left out. For instance, -given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:) +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 InOrd)) +maximum(xs) given (descending given (ListOrd given IntOrd)) ``` -In summary, the argument passed in the definition of minimum is constructed -from the `descending` function applied to the argument `ListOrd`, which is -in turn applied to the argument `IntOrd`. ## Mixing Inferable And Normal Parameters -Inferable parameters can be freely mixed with normal parameter lists. +Inferable parameters can be freely mixed with normal parameters. An inferable parameter may be followed by a normal parameter and _vice versa_. There can be several inferable parameter lists in a definition. Example: ```scala def f given (u: Universe) (x: u.T) given Context = ... -inferred global for Universe { type T = String ... } -inferred ctx for Context { ... } +implied global for Universe { type T = String ... } +implied ctx for Context { ... } ``` Then the following calls are all valid (and normalize to the last one) ```scala @@ -85,18 +83,18 @@ f("abc") given ctx (f given global)("abc") given ctx ``` -## Querying For Inferred Instances +## Querying Implied Instances -A method `infer` in `Predef` creates an inferred instance of a given type. For example, -the inferred instance of `Ord[List[Int]]` is generated by -``` -infer[Ord[List[Int]]] +A method `infer` in `Predef` creates an implied instance of a given type. For example, +the implied instance of `Ord[List[Int]]` is generated by +```scala +infer[Ord[List[Int]]] // reduces to ListOrd given IntOrd ``` -The `infer` method is simply defined as the identity function with an inferable parameter. +The `infer` method is simply defined as the identity function over an inferable parameter. ```scala def infer[T] given (x: T) = x ``` -Functions like `infer` that have only inferable parameters are also called inference _queries_. +Functions like `infer` that have only inferable parameters are also called implicit _queries_. ## Syntax diff --git a/docs/docs/reference/contextual/instance-defs.md b/docs/docs/reference/contextual/instance-defs.md new file mode 100644 index 000000000000..bcf919c02961 --- /dev/null +++ b/docs/docs/reference/contextual/instance-defs.md @@ -0,0 +1,79 @@ +--- +layout: doc-page +title: "Implied Instances" +--- + +Implied instance definitions define "canonical" values of given types +that serve for synthesizing arguments to [inferable parameters](./inferable-params.html). 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 +} + +implied IntOrd for Ord[Int] { + def compare(x: Int, y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 +} + +implied ListOrd[T] given (ord: Ord[T]) for 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 xs1.compareTo(ys1) + } +} +``` +This code defines a trait `Ord` and two implied instance definitions. `IntOrd` defines +an implied instance for the type `Ord[Int]` whereas `ListOrd` defines implied +instances of `Ord[List[T]]` for all types `T` that come with an implied `Ord` instance themselves. +The `given` clause in `ListOrd` defines an [inferable parameter](./inferable-params.html). +Inferable parameters are further explained in the next section. + +## Anonymous Implied Instances + +The name of an implied instance can be left out. So the implied instance definitions +of the last section can also be expressed like this: +```scala +implied for Ord[Int] { ... } +implied [T: Ord] for Ord[List[T]] { ... } +``` +If the name of an instance is missing, the compiler will synthesize a name from +the type(s) in the `for` clause. + +## Implied Alias Instances + +An implied alias instance defines an implied instance that is equal to some expression. E.g., +```scala +implied ctx for ExecutionContext = currentThreadPool().context +``` +Here, we create an implied instance `ctx` of type `ExecutionContext` that resolves to the +right hand side `currentThreadPool().context`. Each time an implied instance of `ExecutionContext` +is demanded, the result of evaluating the right-hand side expression is returned. + +Alias instances may be anonymous, e.g. +```scala +implied for Position = enclosingTree.position +``` +An implied alias instance can have type and context parameters just like any other implied instance definition, but it can only implement a single type. + +## Syntax + +Here is the new syntax of implied instance definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). +``` +TmplDef ::= ... + | ‘implied’ InstanceDef +InstanceDef ::= [id] InstanceParams InstanceBody +InstanceParams ::= [DefTypeParamClause] {InferParamClause} +InferParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) +InstanceBody ::= [‘for’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] + | ‘for’ Type ‘=’ Expr +ContextTypes ::= RefinedType {‘,’ RefinedType} +``` +The identifier `id` can be omitted only if either the `for` part or the template body is present. +If the `for` part is missing, the template body must define at least one extension method. diff --git a/docs/docs/reference/instances/implicit-function-types-spec.md b/docs/docs/reference/contextual/query-types-spec.md similarity index 100% rename from docs/docs/reference/instances/implicit-function-types-spec.md rename to docs/docs/reference/contextual/query-types-spec.md diff --git a/docs/docs/reference/instances/implicit-function-types.md b/docs/docs/reference/contextual/query-types.md similarity index 83% rename from docs/docs/reference/instances/implicit-function-types.md rename to docs/docs/reference/contextual/query-types.md index 5796dde3d4f0..1830eb41d731 100644 --- a/docs/docs/reference/instances/implicit-function-types.md +++ b/docs/docs/reference/contextual/query-types.md @@ -11,7 +11,7 @@ type Contextual[T] = given Context => T A value of query type is applied to inferred arguments, in the same way a method with inferable parameters is applied. For instance: ```scala - inferred ctx for Context = ... + implied ctx for Context = ... def f(x: Int): Contextual[Int] = ... @@ -26,7 +26,7 @@ query literal, `E` is converted to a query literal by rewriting to ``` 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 inferred instances in `E`. +are available as implied instances in `E`. Like query types, query literals are written with a `given` prefix. They differ from normal function literals in two ways: @@ -82,13 +82,13 @@ in terms of query types to avoid the plumbing boilerplate that would otherwise be necessary. ```scala def table(init: given Table => Unit) = { - inferred t for Table + implied t for Table init t } def row(init: given Row => Unit) given (t: Table) = { - inferred r for Row + implied r for Row init t.add(r) } @@ -125,7 +125,7 @@ object PostConditions { def result[T] given (r: WrappedResult[T]): T = WrappedResult.unwrap(r) def (x: T) ensuring [T](condition: given WrappedResult[T] => Boolean): T = { - inferred for WrappedResult[T] = WrappedResult.wrap(x) + implied for WrappedResult[T] = WrappedResult.wrap(x) assert(condition) x } @@ -138,7 +138,13 @@ object Test { ``` **Explanations**: We use a query type `given WrappedResult[T] => Boolean` as the type of the condition of `ensuring`. An argument to `ensuring` such as -`(result == 6)` will therefore have an inferred 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 inferred instances in scope (this is good practice in all cases where inferred 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: +`(result == 6)` will therefore have an implied 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 implied instances in scope (this is good practice in all cases +where inferable 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: { val result = List(1, 2, 3).sum assert(result == 6) @@ -150,4 +156,4 @@ as the type of the condition of `ensuring`. An argument to `ensuring` such as 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.html) +[More details](./query-types-spec.html) diff --git a/docs/docs/reference/instances/relationship-implicits.md b/docs/docs/reference/contextual/relationship-implicits.md similarity index 85% rename from docs/docs/reference/instances/relationship-implicits.md rename to docs/docs/reference/contextual/relationship-implicits.md index 4c8852fee934..cb3a67ea0d53 100644 --- a/docs/docs/reference/instances/relationship-implicits.md +++ b/docs/docs/reference/contextual/relationship-implicits.md @@ -3,16 +3,17 @@ layout: doc-page title: Relationship with Scala 2 Implicits" --- -Many, but not all, of the new implicit features can be mapped to Scala-2 implicits. This page gives a rundown on the relationships between new and old features. +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 Dotty's New Implicits in Scala 2 +# Simulating Contextual Abstraction with Implicits -### Inferred Instance Definitions +### Implied Instance Definitions -Inferred instance definitions can be mapped to combinations of implicit objects, classes and implicit methods. +Implied instance definitions can be mapped to combinations of implicit objects, classes and implicit methods. Instance definitions without parameters are mapped to implicit objects. E.g., ```scala - inferred IntOrd for Ord[Int] { ... } + implied IntOrd for Ord[Int] { ... } ``` maps to ```scala @@ -20,27 +21,27 @@ maps to ``` Parameterized instance definitions are mapped to combinations of classes and implicit methods. E.g., ```scala - inferred ListOrd[T] given (ord: Ord[T]) for Ord[List[T]] { ... } + implied ListOrd[T] given (ord: Ord[T]) for 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] ``` -Inferred alias instances map to implicit methods. E.g., +Implied alias instances map to implicit methods. E.g., ```scala - inferred ctx for ExecutionContext = ... + implied ctx for ExecutionContext = ... ``` maps to ```scala final implicit def ctx: ExecutionContext = ... ``` -### Anonymous Inferred Instances +### Anonymous Implied Instances Anonymous 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` instances above were left out, the following names would be synthesized instead: ```scala - inferred Ord_Int_instance for Ord[Int] { ... } - inferred Ord_List_instance[T] for Ord[List[T]] { ... } + implied Ord_Int_instance for Ord[Int] { ... } + implied Ord_List_instance[T] for Ord[List[T]] { ... } ``` The synthesized type names are formed from @@ -52,7 +53,7 @@ Anonymous instances that define extension methods without also implementing a ty get their name from the name of the first extension method and the toplevel type constructor of its first parameter. For example, the instance ```scala - inferred { + implied { def (xs: List[T]) second[T] = ... } ``` @@ -118,7 +119,7 @@ Implicit conversion methods in Scala 2 can be expressed as instances of the `sca ``` one can write ```scala - inferred stringToToken for Conversion[String, Token] { + implied stringToToken for Conversion[String, Token] { def apply(str: String): Token = new KeyWord(str) } ``` @@ -136,7 +137,7 @@ Implicit `val` definitions in Scala 2 can be expressed in Dotty using a regular can be expressed in Dotty as ```scala lazy val pos: Position = tree.sourcePos - inferred for Position = pos + implied for Position = pos ``` ### Abstract Implicits @@ -148,5 +149,5 @@ An abstract implicit `val` or `def` in Scala 2 can be expressed in Dotty using a can be expressed in Dotty as ``` def symDeco: SymDeco - inferred for SymDeco = symDeco + implied for SymDeco = symDeco ``` diff --git a/docs/docs/reference/instances/replacing-implicits.md b/docs/docs/reference/contextual/replacing-implicits.md similarity index 100% rename from docs/docs/reference/instances/replacing-implicits.md rename to docs/docs/reference/contextual/replacing-implicits.md diff --git a/docs/docs/reference/instances/typeclasses.md b/docs/docs/reference/contextual/typeclasses.md similarity index 77% rename from docs/docs/reference/instances/typeclasses.md rename to docs/docs/reference/contextual/typeclasses.md index 5bbbbfd067b8..f14516692afc 100644 --- a/docs/docs/reference/instances/typeclasses.md +++ b/docs/docs/reference/contextual/typeclasses.md @@ -3,9 +3,9 @@ layout: doc-page title: "Implementing Typeclasses" --- -Inferred instance definitions, extension methods and context bounds +Implied instance definitions, extension methods and context bounds allow a concise and natural expression of _typeclasses_. Typeclasses are just traits -with canonical implementations defined by inferred instance definitions. Here are some examples of standard typeclasses: +with canonical implementations defined by implied instance definitions. Here are some examples of standard typeclasses: ### Semigroups and monoids: @@ -20,12 +20,12 @@ object Monoid { def apply[T] = infer[Monoid[T]] } -inferred for Monoid[String] { +implied for Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } -inferred for Monoid[Int] { +implied for Monoid[Int] { def (x: Int) combine (y: Int): Int = x + y def unit: String = 0 } @@ -48,14 +48,14 @@ trait Monad[F[_]] extends Functor[F] { def pure[A](x: A): F[A] } -inferred ListMonad for Monad[List] { +implied ListMonad for Monad[List] { def (xs: List[A]) flatMap [A, B] (f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } -inferred ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { +implied ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { def (r: Ctx => A) flatMap [A, B] (f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = diff --git a/docs/docs/reference/instances/discussion/context-params.md b/docs/docs/reference/instances/discussion/context-params.md deleted file mode 100644 index afd6992ef5f0..000000000000 --- a/docs/docs/reference/instances/discussion/context-params.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -layout: doc-page -title: "Context Parameters and Arguments" ---- - -This page presents new syntax for defining implicit parameters that aligns definition and call syntax. In both cases, the implicit parameter or argument now follows a `with` connective. -On the definition side, the old syntax -```scala -def f(a: A)(implicit b: B) -``` -is now expressed as -```scala -def f(a: A) with (b: B) -``` -Implicit parameters defined with the new syntax are also called _context parameters_. -They come with a matching syntax for applications: explicit arguments for context parameters are also given after a `with`. - -The following example shows shows three methods that each have a context parameter for `Ord[T]`. -```scala -def maximum[T](xs: List[T]) with (cmp: Ord[T]): T = - xs.reduceLeft((x, y) => if (x < y) y else x) - -def descending[T] with (asc: Ord[T]): Ord[T] = new Ord[T] { - def (x: T) compareTo (y: T) = asc.compareTo(y)(x) -} - -def minimum[T](xs: List[T]) with (cmp: Ord[T]) = - maximum(xs) with descending -``` -The `minimum` method's right hand side defines the explicit argument `descending`. -Explicit arguments for context parameters can be left out. For instance, -given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:) -```scala -maximum(xs) -maximum(xs) with descending -maximum(xs) with (descending with IntOrd) -``` -Arguments for context parameters must be given using the `with` syntax. So the expression `maximum(xs)(descending)` would give a type error. - -The `with` connective is treated like an infix operator with the same precedence as other operators that start with a letter. The expression following a `with` may also be an argument list consisting of several implicit arguments separated by commas. If a tuple should be passed as a single implicit argument (probably an uncommon case), it has to be put in a pair of extra parentheses: -```scala -def f with (x: A, y: B) -f with (a, b) - -def g with (xy: (A, B)) -g with ((a, b)) -``` - -## Implicit Function Types and Closures - -Implicit function types are expressed using the new reserved operator `|=>`. Examples: -```scala -Context |=> T -A |=> B |=> T -(A, B) |=> T -(x: A, y: B) |=> T -``` -The `|=>` syntax was chosen for its resemblance with a turnstile symbol `|-` which signifies context dependencies. - -The old syntax `implicit A => B` is no longer available. -Implicit function types are applied using `with`: -```scala -val f: A |=> B -val a: A -f with a // OK -f(a) // error: `f` does not take parameters -``` -Since application of regular function types and implicit function types different, implicit function types are no longer subtypes of regular function types. - -The `|=>` syntax can also be used for closures. It turns the parameter bindings into implicit -parameters and makes the closure's type an implicit function type -```scala -case class Context(value: String) -val f1: Context |=> String = ctx |=> ctx.value -val f2: Context |=> String = (ctx: Context) |=> ctx.value -val f3: (A, B) |=> T = (a: A, b: B) |=> t -``` -The old syntax `implicit (a: A) => B` now creates a closure of a regular function type `A => B` instead of an implicit function type `A |=> B`. This matches the types of implicit closures in Scala 2.x. - -## Example - -Implementing postconditions via `ensuring`: -```scala -object PostConditions { - opaque type WrappedResult[T] = T - - private instance WrappedResult { - def apply[T](x: T): WrappedResult[T] = x - def (x: WrappedResult[T]) unwrap[T]: T = x - } - - def result[T] with (wrapped: WrappedResult[T]): T = wrapped.unwrap - - instance { - def (x: T) ensuring[T] (condition: WrappedResult[T] |=> Boolean): T = { - assert(condition with WrappedResult(x)) - x - } - } -} - -object Test { - import PostConditions._ - val s = List(1, 2, 3).sum.ensuring(result == 6) -} -``` -## Syntax - -Here is the new syntax of parameters, arguments, and implicit function types seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). -``` -ClsParamClause ::= ... - | ‘with’ ‘(’ [ClsParams] ‘)’ -DefParamClause ::= ... - | ‘with’ ‘(’ [DefParams] ‘)’ -Type ::= ... - | [‘erased’] FunArgTypes ‘|=>’ Type -Expr ::= ... - | [‘erased’] FunParams ‘|=>’ Expr -InfixExpr ::= ... - | InfixExpr ‘with’ (InfixExpr | ParArgumentExprs) -``` diff --git a/docs/docs/reference/instances/discussion/discussion.md b/docs/docs/reference/instances/discussion/discussion.md deleted file mode 100644 index ba38f004aa92..000000000000 --- a/docs/docs/reference/instances/discussion/discussion.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: doc-page -title: "Discussion" ---- - -## Summary - -The instance proposal consists of two main parts: - - - Define a new [high-level syntax for instance definitions](./instance-defs.html) that works out better the intent underlying implicit definitions. - - Define a [new syntax for implicit parameters](./context-params.html) that aligns formal parameters and arguments. - - Define [abstract and alias instances](./replacing-implicits.html) and replace all existing usages of `implicit` in the language. - - -## Migration - -New and old syntax would co-exist initially. Rewrite rules could rewrite old syntax to new automatically. This is trivial in the case of implicit parameters and implicit function types. It is a bit more involved in the case of implicit definitions, since more extensive pattern matching is required to recognize a definition that can be rewritten to a instance. - -To make gradual change possible, we allow the new `with` application syntax also for -old style implicit parameters. - -One tricky question concerns context bounds. During the migration period, should they map to old style implicit parameters or new style context parameters? Mapping them to context parameters could break things in difficult to diagnose ways since then an explicit argument for a context bound would be treated as a type error, or, in the worst case, would be constructed as an argument for an `apply` of the result of method with the -context bound. Also, it would remove context bounds from the common subset that is -treated the same in Scala 2 and 3. We therefore opt to map context bounds to old style implicit parameters for the time being. In the future, migrating context bounds implies a three-step process: - - - Step 1: Deprecate passing arguments to evidence parameters defined by context bounds - directly. All such parameters should be passed with `with`. - - Step 2: Remove ability to pass context bound arguments directly. - - Step 3: Map context bounds to context parameters. - -The third part (replacing existing implicits) should be adopted well after the first two parts are implemented. Alias and abstract instances could be introduced together with the other instance definitions, but could also come later. - -## Discussion - -This is a rather sweeping proposal, which will affect most Scala code. Here are some supporting arguments and a summary of alternatives that were considered. - -The instance definition syntax makes the definition of implicit instances clearer and more concise. People have repeatedly asked for specific "typeclass syntax" in Scala. I believe that instance definitions together with extension methods address this ask quite well. - -Probably the most far reaching and contentious changes affect implicit parameters. There might be resistance to change, because the existing scheme seems to work "well enough". However, I believe there is a price to pay for the status quo. The need to write `.apply` occasionally to force implicit arguments is already bad. Worse is the fact that implicits always have to come last, which makes useful program patterns much more cumbersome than before and makes the language less regular. - -Two alternatives to the proposed syntax changes for implicit parameters were considered: - - 1. Leave `implicit` parameters as they are. This suffers from the problems stated - in the [motivation section](./motivation.md). - 2. Leave the syntax of `implicit` parameters but institute two changes: First, applications - of implicit parameters must be via the pseudo method `.explicitly(...)`. Second, there can be more than one implicit parameter list and implicit parameters may precede explicit ones. This fixes most of the discussed problems, but at the expense of a bulky explicit application syntax. Bulk can be a problem, for instance when the programmer tries to - construct an extensive explicit argument tree to figure out what went wrong with a missing - implicit. Another issue is that migration from old to new scheme would be tricky and - would likely take multiple language versions. - -A contentious point is whether we want abstract and alias instances. As an alternative, one would could also keep the current syntax for implicit vals and defs, which can express the same concepts. The main advantage to introduce abstract and alias instances is that it would -allow us to drop implicit definitions altogether. \ No newline at end of file diff --git a/docs/docs/reference/instances/discussion/instance-defs.md b/docs/docs/reference/instances/discussion/instance-defs.md deleted file mode 100644 index 0d9e8e828dee..000000000000 --- a/docs/docs/reference/instances/discussion/instance-defs.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -layout: doc-page -title: "Instance Definitions" ---- - -Instance definitions provide a concise and uniform syntax for defining implicit values. Example: - -```scala -trait Ord[T] { - def (x: T) compareTo (y: T): Int - def (x: T) < (y: T) = x.compareTo(y) < 0 - def (x: T) > (y: T) = x.compareTo(y) > 0 -} - -instance IntOrd of Ord[Int] { - def (x: Int) compareTo (y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 -} - -instance ListOrd[T: Ord] of Ord[List[T]] { - def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = x.compareTo(y) - if (fst != 0) fst else xs1.compareTo(ys1) - } -} -``` - -Instance can be seen as shorthands for what is currently expressed as implicit definitions. The instance definitions above could also have been formulated as implicits as follows: -```scala -implicit object IntOrd extends Ord[Int] { - def (x: Int) compareTo (y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 -} - -class ListOrd[T: Ord] extends Ord[List[T]] { - def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = x.compareTo(y) - if (fst != 0) fst else xs1.compareTo(ys1) - } -} -implicit def ListOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] -``` -In fact, a plausible compilation strategy would map the instance definitions given above to exactly these implicit definitions. - -Implicit definitions are kept for the moment but should be be deprecated eventually. As we will see, the only kind of implicit definitions that cannot be directly emulated by instance definitions are implicit conversions. - -Why prefer instance over implicit definitions? Their definitions are shorter, more uniform, and they focus on intent rather than mechanism: I.e. we define an _instance of_ a type, instead of an _implicit object_ that happens to _extend_ a type. Likewise, the `ListOrd` instance is shorter and clearer than the class/implicit def combo that emulates it. Arguably, `implicit` was always a misnomer. An `implicit object` is every bit as explicit as a plain object, it's just that the former is eligible as a synthesized (implicit) argument to an _implicit parameter_. So, "implicit" makes sense as an adjective for arguments and at a stretch for parameters, but not so much for the other kinds of definitions. - -## Instances for Extension Methods - -Instances can also be defined without an `of` clause. A typical application is to use a instance to package some extension methods. Examples: - -```scala -instance StringOps { - def (xs: Seq[String]) longestStrings: Seq[String] = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } -} - -instance ListOps { - def (xs: List[T]) second[T] = xs.tail.head -} -``` -Instances like these translate to `implicit` objects without an extends clause. - -## Anonymous Instances - -The name of an instance definition can be left out. Examples: -```scala -instance of Ord[Int] { ... } -instance [T: Ord] of Ord[List[T]] { ... } - -instance { - def (xs: List[T]) second[T] = xs.tail.head -} -``` -If the name of an instance is missing, the compiler will synthesize a name from -the type in the of clause, or, if that is missing, from the first defined -extension method. Details remain to be specified. - -## Conditional Instances - -An instance definition can depend on another instance being defined. For instance: -```scala -trait Convertible[From, To] { - def convert(x: From): To -} - -instance [From, To] with (c: Convertible[From, To]) of Convertible[List[From], List[To]] { - def convert(x: List[From]): List[To] = x.map(c.convert) -} -``` - -The `with` clause instance defines required instances. The instance of `Convertible[List[From], List[To]]` above is defined only if an instance of `Convertible[From, To]` exists. -`with` clauses translate to implicit parameters of implicit methods. Here is the expansion of the anonymous instance above in terms of a class and an implicit method (the example demonstrates well the reduction in boilerplate afforded by instance syntax): -```scala -class Convertible_List_List_instance[From, To](implicit c: Convertible[From, To]) -extends Convertible[List[From], List[To]] { - def convert (x: List[From]): List[To] = x.map(c.convert) -} -implicit def Convertible_List_List_instance[From, To](implicit c: Convertible[From, To]) - : Convertible[List[From], List[To]] = - new Convertible_List_List_instance[From, To] -``` -Context bounds in instance definitions also translate to implicit parameters, and therefore they can be represented alternatively as with clauses. For instance, here is an equivalent definition of the `ListOrd` instance: -```scala -instance ListOrd[T] with (ord: Ord[T]) of List[Ord[T]] { ... } -``` -An underscore ‘_’ can be used as the name of a required instance, if that instance does not -need to be referred to directly. For instance, the last `ListOrd` instance could also have been written like this: -```scala -instance ListOrd[T] with (_: Ord[T]) of List[Ord[T]] { ... } -``` - -**Design note:** An alternative to the underscore syntax would be to allow the `name:` part to be left out entirely. I.e. it would then be `instance ListOrd[T] with (Ord[T]) of ...`. I am not yet sure which is preferable. - - -## Typeclass Instances - -Here are some examples of standard typeclass instances: - -Semigroups and monoids: - -```scala -trait SemiGroup[T] { - def (x: T) combine (y: T): T -} -trait Monoid[T] extends SemiGroup[T] { - def unit: T -} - -instance of Monoid[String] { - def (x: String) combine (y: String): String = x.concat(y) - def unit: String = "" -} - -def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(infer[Monoid[T]].unit)(_.combine(_)) -``` -Functors and monads: -```scala -trait Functor[F[_]] { - def (x: F[A]) map[A, B] (f: A => B): F[B] -} - -trait Monad[F[_]] extends Functor[F] { - def (x: F[A]) flatMap[A, B] (f: A => F[B]): F[B] - def (x: F[A]) map[A, B] (f: A => B) = x.flatMap(f `andThen` pure) - - def pure[A](x: A): F[A] -} - -instance ListMonad of Monad[List] { - def (xs: List[A]) flatMap[A, B] (f: A => List[B]): List[B] = - xs.flatMap(f) - def pure[A](x: A): List[A] = - List(x) -} - -instance ReaderMonad[Ctx] of Monad[[X] => Ctx => X] { - def (r: Ctx => A) flatMap[A, B] (f: A => Ctx => B): Ctx => B = - ctx => f(r(ctx))(ctx) - def pure[A](x: A): Ctx => A = - ctx => x -} -``` - -## Syntax - -Here is the new syntax of instance definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). -``` -TmplDef ::= ... - | ‘instance’ InstanceDef -InstanceDef ::= [id] InstanceParams [‘of’ ConstrApps] [TemplateBody] -InstanceParams ::= [DefTypeParamClause] {‘with’ ‘(’ [DefParams] ‘)} -``` -The identifier `id` can be omitted only if either the `of` part or the template body is present. If the `of` part is missing, the template body must define at least one extension method. \ No newline at end of file diff --git a/docs/docs/reference/instances/discussion/motivation.md b/docs/docs/reference/instances/discussion/motivation.md deleted file mode 100644 index 9fc239406404..000000000000 --- a/docs/docs/reference/instances/discussion/motivation.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: doc-page -title: "Motivation" ---- - -### Critique - -Scala's implicits are its most distinguished feature. They are _the_ fundamental way to abstract over context. They represent a single concept with an extremely varied number of use cases, among them: implementing type classes, establishing context, dependency injection, expressing capabilities, computing new types and proving relationships between them. - -At the same time, implicits are also a controversial feature. I believe there are several reasons for this. - -_First_, 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 - - 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. - -_Second_, 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. The dotty compiler implements some improvements in this case, but further progress would be desirable. - -_Third_, the syntax of implicit definitions might be a bit 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 often conveys mechanism better than intent. For instance, a typeclass instance is an implicit object or val if unconditional and an implicit def with implicit parameters 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. - -_Fourth_, the syntax of implicit parameters has also some shortcomings. It starts with the position of `implicit` as a pseudo-modifier that applies to a whole parameter section instead of a single parameter. This represents an irregular case wrt to the rest of Scala's syntax. Furthermore, 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. - -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 more cumbersome and less clear than it could be. - -Can implicit function types help? Implicit function types allow to abstract over implicit parameterization. They are a key part of the program to make as many aspects of methods as possible first class. Implicit function types can avoid much of the repetition in programs that use implicits widely. But they do not directly address the issues mentioned here. - -### Alternative Design - -`implicit` is a modifier that gets attached to various constructs. -I.e. we talk about implicit vals, defs, objects, parameters, or arguments. -This conveys mechanism rather than intent. What _is_ the intent that we want to convey? -Ultimately it's "trade types for terms". The programmer specifies a type and the compiler -fills in the term matching that type automatically. So the concept we are after would -serve to express definitions that provide the canonical _instances_ for certain types. - -The next sections elaborate this alternative design. It consists of the following pages: - - - a proposal to replace implicit _definitions_ by [instance definitions](./instance-defs.md), - - a proposal for a [new syntax](./context-params.md) of implicit _parameters_ and their _arguments_, - - updates to the syntax for [implicit function types and closures](./implicit-function-types.md), - - a new way to express [implicit conversions](./implicit-conversions.md) as instances of a special trait, diff --git a/docs/docs/reference/instances/discussion/replacing-implicits.md b/docs/docs/reference/instances/discussion/replacing-implicits.md deleted file mode 100644 index 514379d2e75b..000000000000 --- a/docs/docs/reference/instances/discussion/replacing-implicits.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -layout: doc-page -title: "Replacing Implicits" ---- - -The previous two pages proposed high-level syntax for implicit definitions and a new syntax for implicit parameters. - -This addresses all the issues mentioned in the [Motivation](./motivation.md), but it leaves us with two related constructs: new style instance definitions and context parameters and traditional implicits. This page discusses what would be needed to get rid of `implicit` entirely. - -## Abstract and Alias Instances - -Instance definitions can be abstract. -As an example of an abstract instance, consider the following fragment that's derived from Scala's Tasty extractor framework: -```scala -trait TastyAPI { - type Symbol - trait SymDeco { - def (sym: Symbol) name: Name - def (sym: Symbol) tpe: Type - } - instance symDeco: SymDeco -} -``` -Here, `symDeco` is available as a instance of the `SymDeco` trait but its actual implementation -is deferred to subclasses of the `TastyAPI` trait. - -An example of an alias instance would be an implementation of `symDeco` in terms of some internal compiler structure: -```scala -trait TastyImpl extends TastyAPI { - instance symDeco: SymDeco = compilerSymOps -} -``` -Note that the result type of an abstract or alias instance is introduced with a colon instead of an `of`. This seems more natural since it evokes the similarity to implicit parameters, whose type is also given following a `:`. It also avoids the syntactic ambiguity with an instance definition of a class that does not add any new definitions. I.e. -```scala -instance a of C // concrete instance of class C, no definitions added -instance b: C // abstract instance of class C -``` -Further examples of alias instances: -```scala -instance ctx = outer.ctx -instance ctx: Context = outer.ctx -instance byNameCtx with (): Context = outer.ctx -instance f[T]: C[T] = new C[T] -instance g with (ctx: Context): D = new D(ctx) -``` -As another example, if one had already defined classes `IntOrd` and `ListOrd`, instances for them could be defined as follows: -```scala -class IntOrd extends Ord[Int] { ... } -class ListOrd[T: Ord] extends Ord[List[T]] { ... } - -instance intOrd: Ord[Int] = new IntOrd -instance listOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] -``` -The result type of a alias instance is mandatory unless the instance definition -occurs as a statement in a block and lacks any type or value parameters. This corresponds to the same restriction for implicit vals in Dotty. - -Abstract instances are equivalent to abstract implicit defs. Alias instances are equivalent to implicit defs if they are parameterized or to implicit vals otherwise. For instance, the instances defined so far in this section are equivalent to: -```scala -implicit def symDeco: SymDeco -implicit val symDeco: SymDeco = compilerSymOps - -implicit val ctx = outer.ctx -implicit val ctx: Context = outer.ctx -implicit def byNameCtx: Ctx = outer.ctx -implicit def f[T]: C[T] = new C[T] -implicit def g(implicit ctx: Context): D = new D(ctx) - -implicit val intOrd: Ord[Int] = new IntOrd -implicit def listOrd(implicit ev: Ord[T]): Ord[List[T]] = new ListOrd[T] -``` -The `lazy` modifier is applicable to unparameterized alias instances. If present, the resulting implicit val is lazy. For instance, -```scala -lazy instance intOrd2: Ord[Int] = new IntOrd -``` -would be equivalent to -```scala -lazy implicit val intOrd2: Ord[Int] = new IntOrd -``` - -## Implicit Conversions and Classes - -The only use cases that are not yet covered by the proposal are implicit conversions and implicit classes. We do not propose to use `instance` in place of `implicit` for these, since that would bring back the uncomfortable similarity between implicit conversions and parameterized implicit aliases. However, there is a way to drop implicit conversions entirely. Scala 3 already [defines](https://github.com/lampepfl/dotty/pull/2065) a class `Conversion` whose instances are available as implicit conversions. -```scala - abstract class Conversion[-T, +U] extends Function1[T, U] -``` -One can define all implicit conversions as instances of this class. E.g. -```scala -instance StringToToken of Conversion[String, Token] { - def apply(str: String): Token = new KeyWord(str) -} -``` -The fact that this syntax is more verbose than simple implicit defs could be a welcome side effect since it might dampen any over-enthusiasm for defining implicit conversions. - -That leaves implicit classes. Most use cases of implicit classes are probably already covered by extension methods. For the others, one could always fall back to a pair of a regular class and an `Conversion` instance. It would be good to do a survey to find out how many classes would be affected. - -## Summoning an Instance - -Besides `implicit`, there is also `implicitly`, a method defined in `Predef` that computes an implicit value for a given type. A possible replacement could be `instanceOf`. Or, keeping with common usage, one could introduce the name `summon` for this operation. So `summon[T]` summons an instance of `T`, in the same way as `implicitly[T]` did. The definition of `summon` is straightforward: -```scala -def summon[T] with (x: T) = x -``` - -## Syntax - -The syntax changes for this page are summarized as follows: -``` -InstanceDef ::= ... - | id InstanceParams ‘:’ Type ‘=’ Expr - | id ‘=’ Expr -``` -In addition, the `implicit` modifier is removed together with all [productions]((http://dotty.epfl.ch/docs/internals/syntax.html) that reference it. diff --git a/docs/docs/reference/instances/instance-defs.md b/docs/docs/reference/instances/instance-defs.md deleted file mode 100644 index 35aeccacae03..000000000000 --- a/docs/docs/reference/instances/instance-defs.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -layout: doc-page -title: "Inferred Instances" ---- - -Inferred instance definitions define "canonical" values of given types -that serve for synthesizing arguments to [inferable parameters](./inferable-params.html). 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 -} - -inferred IntOrd for Ord[Int] { - def compare(x: Int, y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 -} - -inferred ListOrd[T: Ord] for 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 xs1.compareTo(ys1) - } -} -``` -This code defines a trait `Ord` and two inferred instance definitions. `IntOrd` defines -an inferred instance for the type `Ord[Int]` whereas `ListOrd` defines inferred -instances of `Ord[List[T]]` for any type `T` that comes with an inferred `Ord` instance itself. -The `given` clause in `ListOrd` defines an [implicit parameter](./implicit-params.html). -Implicit parameters are further explained in the next section. - -## Anonymous Inferred Instances - -The name of an inferred instance can be left out. So the inferred instance definitions -of the last section can also be expressed like this: -```scala -inferred for Ord[Int] { ... } -inferred [T: Ord] for Ord[List[T]] { ... } - -If the name of an instance is missing, the compiler will synthesize a name from -the type(s) in the `for` clause. - -## Inferred Alias Instances - -An inferred alias instance creates an inferred instance that is equal to -some expression. E.g., -``` -inferred ctx for ExecutionContext = currentThreadPool().context -``` -Here, we create an inferred instance `ctx` of type `ExecutionContext` that resolves to the -right hand side `currentThreadPool().context`. Each time an inferred instance of `ExecutionContext` -is demanded, the result of evaluating the right-hand side expression is returned. The instance definition is equivalent to the following implicit definition: -``` -final implicit def ctx: ExecutionContext = currentThreadPool().context -``` -Alias instances may be anonymous, e.g. -``` -inferred for Position = enclosingTree.position -``` -An inferred alias instance can have type and context parameters just like any other inferred instance definition, but it can only implement a single type. - -## Syntax - -Here is the new syntax of inferred instance definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). -``` -TmplDef ::= ... - | ‘inferred’ InstanceDef -InstanceDef ::= [id] InstanceParams InstanceBody -InstanceParams ::= [DefTypeParamClause] {InferParamClause} -InferParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) -InstanceBody ::= [‘for’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] - | ‘for’ Type ‘=’ Expr -ContextTypes ::= RefinedType {‘,’ RefinedType} -``` -The identifier `id` can be omitted only if either the `for` part or the template body is present. -If the `for` part is missing, the template body must define at least one extension method. diff --git a/docs/docs/reference/other-new-features/extension-methods.md b/docs/docs/reference/other-new-features/extension-methods.md deleted file mode 100644 index 8f8c13c51978..000000000000 --- a/docs/docs/reference/other-new-features/extension-methods.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -layout: doc-page -title: "Extension Methods" ---- - -Extension methods allow one to add methods to a type after the type is defined. Example: - -```scala -case class Circle(x: Double, y: Double, radius: Double) - -def (c: Circle) circumference: Double = c.radius * math.Pi * 2 -``` - -Like regular methods, extension methods can be invoked with infix `.`: - -```scala - val circle = Circle(0, 0, 1) - circle.circumference -``` - -### Translation of Extension Methods - -Extension methods are methods that have a parameter clause in front of the defined -identifier. They translate to methods where the leading parameter section is moved -to after the defined identifier. So, the definition of `circumference` above translates -to the plain method, and can also be invoked as such: -```scala -def circumference(c: Circle): Double = c.radius * math.Pi * 2 - -assert(circle.circumference == circumference(circle)) -``` - -### Translation of Calls to Extension Methods - -When is an extension method applicable? There are two possibilities. - - - An extension method is applicable if it is visible under a simple name, by being defined - or inherited or imported in a scope enclosing the application. - - An extension method is applicable if it is a member of an eligible implicit value at the point of the application. - -As an example, consider an extension method `longestStrings` on `String` defined in a trait `StringSeqOps`. - -```scala -trait StringSeqOps { - def (xs: Seq[String]) longestStrings = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } -} -``` -We can make the extension method available by defining an inferred instance of `StringSeqOps`, like this: -```scala -implicit object ops1 extends StringSeqOps -``` -Then -```scala -List("here", "is", "a", "list").longestStrings -``` -is legal everywhere `StringSeqOps1` is available as an implicit value. Alternatively, we can define `longestStrings` -as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. - -```scala -object ops2 extends StringSeqOps -import ops2.longestStrings -List("here", "is", "a", "list").longestStrings -``` -The precise rules for resolving a selection to an extension method are as follows. - -Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, -and where `T` is the expected type. The following two rewritings are tried in order: - - 1. The selection is rewritten to `m[Ts](e)`. - 2. If the first rewriting does not typecheck with expected type `T`, and there is an implicit value `i` - in either the current scope or in the implicit scope of `T`, and `i` defines an extension - method named `m`, then selection is expanded to `i.m[Ts](e)`. - 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. - -So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided -`circle` has type `Circle` and `CircleOps` is an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). - -### Operators - -The extension method syntax also applies to the definition of operators. -In each case the definition syntax mirrors the way the operator is applied. -Examples: -``` - def (x: String) < (y: String) = ... - def (x: Elem) +: (xs: Seq[Elem]) = ... - - "ab" + "c" - 1 +: List(2, 3) -``` -The two definitions above translate to -``` - def < (x: String)(y: String) = ... - def +: (xs: Seq[Elem])(x: Elem) = ... -``` -Note that swap of the two parameters `x` and `xs` when translating -the right-binding operator `+:` to an extension method. This is analogous -to the implementation of right binding operators as normal methods. - -### Generic Extensions - -The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible to extend a generic type by adding type parameters to an extension method: - -```scala -def (xs: List[T]) second [T] = xs.tail.head -``` - -or: - - -```scala -def (xs: List[List[T]]) flattened [T] = xs.foldLeft[List[T]](Nil)(_ ++ _) -``` - -or: - -```scala -def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y) -``` - -As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the preceding parameter clause. - - -### Extension Methods and Type Classes - -The rules for expanding extension methods make sure that they work seamlessly with type classes. For instance, consider `SemiGroup` and `Monoid`. -```scala - // Two type classes: - trait SemiGroup[T] { - def (x: T) combine(y: T): T - } - trait Monoid[T] extends SemiGroup[T] { - def unit: T - } - object Monoid { - // An instance declaration: - inferred StringMonoid for Monoid[String] { - def (x: String) combine (y: String): String = x.concat(y) - def unit: String = "" - } - } - - // Abstracting over a typeclass with a context bound: - def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) -``` - -In the last line, the call to `_.combine(_)` expands to `(x1, x2) => x1.combine(x2)`, -which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the inferred -evidence parameter summoned by the context bound `[T: Monoid]`. This works since -extension methods apply everywhere their enclosing object is available as an implicit. - -To avoid having to write `summon[Monoid[T]].unit` to access the `unit` method in `Monoid[T]`, -we can make `unit` itself an extension method on the `Monoid` _companion object_, -as shown below: - -```scala - trait Monoid[T] extends SemiGroup[T] { - def (self: Monoid.type) unit: T - } - object Monoid { - inferred StringMonoid for Monoid[String] { - def (x: String) combine (y: String): String = x.concat(y) - def (self: Monoid.type) unit: String = "" - } - } -``` - -This allows us to write `Monoid.unit` instead of `summon[Monoid[T]].unit`, -letting the expected type distinguish which instance we want to use: - -```scala - def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(Monoid.unit)(_.combine(_)) -``` - -### Syntax - -The required syntax extension just adds one clause for extension methods relative -to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.html). -``` -DefSig ::= ... - | ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses -``` - - - - diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 01bebbf630b9..bea5fc5ed4e6 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -40,26 +40,26 @@ sidebar: - title: Translation url: docs/reference/enums/desugarEnums.html - title: Contextual Abstractions - - title: Inferred Instances - url: docs/reference/instances/instance-defs.html + - title: Implied Instances + url: docs/reference/contextual/instance-defs.html - title: Inferable Parameters - url: docs/reference/instances/context-params.html + url: docs/reference/contextual/inferable-params.html - title: Context Bounds - url: docs/reference/instances/context-bounds.html + url: docs/reference/contextual/context-bounds.html - title: Extension Methods - url: docs/reference/instances/extension-methods.html + url: docs/reference/contextual/extension-methods.html - title: Implementing Typeclasses - url: docs/reference/instances/typeclasses.html + url: docs/reference/contextual/typeclasses.html - title: Typeclass Derivation url: docs/reference/derivation.html - - title: Implicit Function Types - url: docs/reference/instances/implicit-function-types.html - - title: Implicit Conversions - url: docs/reference/instances/implicit-conversions.html - - title: Implicit By-Name Parameters - url: docs/reference/instances/implicit-by-name-parameters.html + - title: Query Types + url: docs/reference/contextual/query-types.html + - title: Implied Conversions + url: docs/reference/contextual/conversions.html + - title: Inferable By-Name Parameters + url: docs/reference/contextual/inferable-by-name-parameters.html - title: Relationship with Scala 2 Implicits - url: docs/reference/instances/relationship-implicits.html + url: docs/reference/contextual/relationship-implicits.html - title: Other New Features subsection: - title: Multiversal Equality diff --git a/tests/new/test.scala b/tests/new/test.scala index 3e568da314df..728f2173d100 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -4,10 +4,10 @@ trait T { object Test0 { trait A[T] - instance a[T] of A[T] + implied a[T] for A[T] class B[T] - instance b[T] of B[T] + implied b[T] for B[T] } class C extends T diff --git a/tests/pos/givenIn.scala b/tests/pos/givenIn.scala index c7f446077b8b..798d0411d5ff 100644 --- a/tests/pos/givenIn.scala +++ b/tests/pos/givenIn.scala @@ -3,7 +3,7 @@ object Test { class Context { inline def givenIn[T](op: => given Context => T) = { - instance of Context = this + implied for Context = this op } } diff --git a/tests/pos/implicit-conversion.scala b/tests/pos/implicit-conversion.scala index d6bc9f6be86c..bffc6745060c 100644 --- a/tests/pos/implicit-conversion.scala +++ b/tests/pos/implicit-conversion.scala @@ -1,6 +1,6 @@ object Test { // a problematic implicit conversion, should we flag it? - inferred for Conversion[String, Int] { + implied for Conversion[String, Int] { def apply(x: String): Int = Integer.parseInt(toString) } } \ No newline at end of file diff --git a/tests/pos/reference/instances.scala b/tests/pos/reference/instances.scala index 01d5f577e245..925fec9ca6ae 100644 --- a/tests/pos/reference/instances.scala +++ b/tests/pos/reference/instances.scala @@ -32,12 +32,12 @@ class Common { object Instances extends Common { - inferred IntOrd for Ord[Int] { + implied IntOrd for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 } - inferred ListOrd[T] given Ord[T] for Ord[List[T]] { + implied ListOrd[T] given Ord[T] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -48,25 +48,25 @@ object Instances extends Common { } } - inferred StringOps { + implied StringOps { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } - inferred ListOps { + implied ListOps { def (xs: List[T]) second[T] = xs.tail.head } - inferred ListMonad for Monad[List] { + implied ListMonad for Monad[List] { def (xs: List[A]) flatMap[A, B] (f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } - inferred ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { + implied ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { def (r: Ctx => A) flatMap[A, B] (f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = @@ -105,7 +105,7 @@ object Instances extends Common { def (sym: Symbol) name: String } def symDeco: SymDeco - inferred for SymDeco = symDeco + implied for SymDeco = symDeco } object TastyImpl extends TastyAPI { type Symbol = String @@ -119,20 +119,20 @@ object Instances extends Common { class C given (ctx: Context) { def f() = { locally { - inferred for Context = this.ctx + implied for Context = this.ctx println(infer[Context].value) } locally { lazy val ctx1 = this.ctx - inferred for Context = ctx1 + implied for Context = ctx1 println(infer[Context].value) } locally { - inferred d[T] for D[T] + implied d[T] for D[T] println(infer[D[Int]]) } locally { - inferred given Context for D[Int] + implied given Context for D[Int] println(infer[D[Int]]) } } @@ -140,7 +140,7 @@ object Instances extends Common { class Token(str: String) - inferred StringToToken for Conversion[String, Token] { + implied StringToToken for Conversion[String, Token] { def apply(str: String): Token = new Token(str) } @@ -150,14 +150,14 @@ object Instances extends Common { object PostConditions { opaque type WrappedResult[T] = T - private object WrappedResult { + private implied WrappedResult { def apply[T](x: T): WrappedResult[T] = x def (x: WrappedResult[T]) unwrap[T]: T = x } def result[T] given (wrapped: WrappedResult[T]): T = wrapped.unwrap - inferred { + implied { def (x: T) ensuring[T] (condition: given WrappedResult[T] => Boolean): T = { assert(condition given WrappedResult(x)) x @@ -166,12 +166,12 @@ object PostConditions { } object AnonymousInstances extends Common { - inferred for Ord[Int] { + implied for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 } - inferred [T: Ord] for Ord[List[T]] { + implied [T: Ord] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -182,22 +182,22 @@ object AnonymousInstances extends Common { } } - inferred { + implied { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } - inferred { + implied { def (xs: List[T]) second[T] = xs.tail.head } - inferred [From, To] given (c: Convertible[From, To]) for Convertible[List[From], List[To]] { + implied [From, To] given (c: Convertible[From, To]) for Convertible[List[From], List[To]] { def (x: List[From]) convert: List[To] = x.map(c.convert) } - inferred for Monoid[String] { + implied for Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -272,13 +272,13 @@ object Completions { } // conversions defining the possible arguments to pass to `complete` - inferred stringArg for Conversion[String, CompletionArg] { + implied stringArg for Conversion[String, CompletionArg] { def apply(s: String) = CompletionArg.Error(s) } - inferred responseArg for Conversion[Future[HttpResponse], CompletionArg] { + implied responseArg for Conversion[Future[HttpResponse], CompletionArg] { def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) } - inferred statusArg for Conversion[Future[StatusCode], CompletionArg] { + implied statusArg for Conversion[Future[StatusCode], CompletionArg] { def apply(code: Future[StatusCode]) = CompletionArg.Status(code) } } \ No newline at end of file diff --git a/tests/run/instances-anonymous.scala b/tests/run/instances-anonymous.scala index a4b29bdcb9b8..1c41cb25e19e 100644 --- a/tests/run/instances-anonymous.scala +++ b/tests/run/instances-anonymous.scala @@ -8,7 +8,7 @@ object Test extends App { case class Circle(x: Double, y: Double, radius: Double) - instance { + implied { def (c: Circle) circumference: Double = c.radius * math.Pi * 2 } @@ -16,7 +16,7 @@ object Test extends App { println(circle.circumference) - instance { + implied { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) @@ -25,13 +25,13 @@ object Test extends App { val names = List("hi", "hello", "world") assert(names.longestStrings == List("hello", "world")) - instance { + implied { def (xs: Seq[T]) second[T] = xs.tail.head } assert(names.longestStrings.second == "world") - instance { + implied { def (xs: List[List[T]]) flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _) } @@ -45,8 +45,8 @@ object Test extends App { def unit: T } - // An instance declaration: - instance of Monoid[String] { + // An implied declaration: + implied for Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -64,13 +64,13 @@ object Test extends App { val minimum: T } - instance of Ord[Int] { + implied for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 val minimum = Int.MinValue } - instance [T: Ord] of Ord[List[T]] { + implied [T: Ord] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -102,14 +102,14 @@ object Test extends App { def pure[A](x: A): F[A] } - instance of Monad[List] { + implied for Monad[List] { def (xs: List[A]) flatMap[A, B] (f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } - instance [Ctx] of Monad[[X] => Ctx => X] { + implied [Ctx] for Monad[[X] => Ctx => X] { def (r: Ctx => A) flatMap[A, B] (f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = diff --git a/tests/run/instances.scala b/tests/run/instances.scala index c971238330f2..fc3fe8e9f5c2 100644 --- a/tests/run/instances.scala +++ b/tests/run/instances.scala @@ -8,7 +8,7 @@ object Test extends App { case class Circle(x: Double, y: Double, radius: Double) - instance CircleOps { + implied CircleOps { def (c: Circle) circumference: Double = c.radius * math.Pi * 2 } @@ -16,7 +16,7 @@ object Test extends App { assert(circle.circumference == CircleOps.circumference(circle)) - instance StringOps { + implied StringOps { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) @@ -25,18 +25,18 @@ object Test extends App { val names = List("hi", "hello", "world") assert(names.longestStrings == List("hello", "world")) - instance SeqOps { + implied SeqOps { def (xs: Seq[T]) second[T] = xs.tail.head } assert(names.longestStrings.second == "world") - instance ListListOps { + implied ListListOps { def (xs: List[List[T]]) flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _) } // A right associative op - instance Prepend { + implied Prepend { def (x: T) ::[T] (xs: Seq[T]) = x +: xs } val ss: Seq[Int] = List(1, 2, 3) @@ -53,8 +53,8 @@ object Test extends App { def unit: T } - // An instance declaration: - instance StringMonoid of Monoid[String] { + // An implied declaration: + implied StringMonoid for Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -72,13 +72,13 @@ object Test extends App { val minimum: T } - instance of Ord[Int] { + implied for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 val minimum = Int.MinValue } - instance ListOrd[T: Ord] of Ord[List[T]] { + implied ListOrd[T: Ord] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -110,14 +110,14 @@ object Test extends App { def pure[A](x: A): F[A] } - instance ListMonad of Monad[List] { + implied ListMonad for Monad[List] { def (xs: List[A]) flatMap[A, B] (f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } - instance ReaderMonad[Ctx] of Monad[[X] => Ctx => X] { + implied ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { def (r: Ctx => A) flatMap[A, B] (f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = diff --git a/tests/run/tagless.scala b/tests/run/tagless.scala index d764f696577b..e14a0c12b25e 100644 --- a/tests/run/tagless.scala +++ b/tests/run/tagless.scala @@ -40,13 +40,13 @@ object Test extends App { add(lit(8), neg(add(lit(1), lit(2)))) // Base operations as typeclasses - inferred for Exp[Int] { + implied for Exp[Int] { def lit(i: Int): Int = i def neg(t: Int): Int = -t def add(l: Int, r: Int): Int = l + r } - inferred for Exp[String] { + implied for Exp[String] { def lit(i: Int): String = i.toString def neg(t: String): String = s"(-$t)" def add(l: String, r: String): String = s"($l + $r)" @@ -67,11 +67,11 @@ object Test extends App { def tfm1[T: Exp : Mult] = add(lit(7), neg(mul(lit(1), lit(2)))) def tfm2[T: Exp : Mult] = mul(lit(7), tf1) - inferred for Mult[Int] { + implied for Mult[Int] { def mul(l: Int, r: Int): Int = l * r } - inferred for Mult[String] { + implied for Mult[String] { def mul(l: String, r: String): String = s"$l * $r" } @@ -87,7 +87,7 @@ object Test extends App { } import Tree._ - inferred for Exp[Tree], Mult[Tree] { + implied for Exp[Tree], Mult[Tree] { def lit(i: Int): Tree = Node("Lit", Leaf(i.toString)) def neg(t: Tree): Tree = Node("Neg", t) def add(l: Tree, r: Tree): Tree = Node("Add", l , r) @@ -108,7 +108,7 @@ object Test extends App { private class Exc(msg: String) extends Exception(msg) def _throw(msg: String) given CanThrow: Nothing = throw new Exc(msg) def _try[T](op: Maybe[T])(handler: String => T): T = { - inferred for CanThrow + implied for CanThrow try op catch { case ex: Exception => handler(ex.getMessage) @@ -153,7 +153,7 @@ object Test extends App { def value[T] given Exp[T]: T } - inferred for Exp[Wrapped] { + implied for Exp[Wrapped] { def lit(i: Int) = new Wrapped { def value[T] given (e: Exp[T]): T = e.lit(i) } @@ -196,7 +196,7 @@ object Test extends App { // Added operation: negation pushdown enum NCtx { case Pos, Neg } - inferred [T] given (e: Exp[T]) for Exp[NCtx => T] { + implied [T] given (e: Exp[T]) for Exp[NCtx => T] { import NCtx._ def lit(i: Int) = { case Pos => e.lit(i) @@ -216,7 +216,7 @@ object Test extends App { println(pushNeg(tf1[NCtx => String])) println(pushNeg(pushNeg(pushNeg(tf1))): String) - inferred [T] given (e: Mult[T]) for Mult[NCtx => T] { + implied [T] given (e: Mult[T]) for Mult[NCtx => T] { import NCtx._ def mul(l: NCtx => T, r: NCtx => T): NCtx => T = { case Pos => e.mul(l(Pos), r(Pos)) @@ -230,7 +230,7 @@ object Test extends App { import IExp._ // Going from type class encoding to ADT encoding - inferred initialize for Exp[IExp] { + implied initialize for Exp[IExp] { def lit(i: Int): IExp = Lit(i) def neg(t: IExp): IExp = Neg(t) def add(l: IExp, r: IExp): IExp = Add(l, r) From e2f129953cbd0f3f903a40da08c004c86ce3737f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 31 Jan 2019 15:37:05 +0100 Subject: [PATCH 12/16] Tweaks to wordings in docs Also: drop some doc pages that are no longer used. --- .../reference/contextual/context-bounds.md | 8 +- docs/docs/reference/contextual/conversions.md | 6 +- docs/docs/reference/contextual/derivation.md | 6 +- .../reference/contextual/extension-methods.md | 34 +- .../contextual/implicit-conversions.md | 78 ---- .../inferable-by-name-parameters.md | 11 +- .../reference/contextual/inferable-params.md | 2 +- .../reference/contextual/query-types-spec.md | 23 +- docs/docs/reference/contextual/query-types.md | 21 +- .../contextual/relationship-implicits.md | 75 ++-- .../contextual/replacing-implicits.md | 56 --- docs/docs/reference/derivation.md | 392 ------------------ .../reference/dropped-features/limit22.md | 10 +- docs/sidebar.yml | 2 +- 14 files changed, 100 insertions(+), 624 deletions(-) delete mode 100644 docs/docs/reference/contextual/implicit-conversions.md delete mode 100644 docs/docs/reference/contextual/replacing-implicits.md delete mode 100644 docs/docs/reference/derivation.md diff --git a/docs/docs/reference/contextual/context-bounds.md b/docs/docs/reference/contextual/context-bounds.md index 8a79cf349eed..3458c5cf6cd1 100644 --- a/docs/docs/reference/contextual/context-bounds.md +++ b/docs/docs/reference/contextual/context-bounds.md @@ -9,16 +9,16 @@ A context bound is a shorthand for expressing a common pattern of an inferable p ```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 inferred parameter `given Ord[T]`. The inferred 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 an inferable parameter `given Ord[T]`. The inferable 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 ``` would expand to -``` +```scala def f[T, U](x: T) given (y: U, z: V) given 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 = ... ``` diff --git a/docs/docs/reference/contextual/conversions.md b/docs/docs/reference/contextual/conversions.md index 8bf9aeacb91b..4f169670982a 100644 --- a/docs/docs/reference/contextual/conversions.md +++ b/docs/docs/reference/contextual/conversions.md @@ -1,6 +1,6 @@ --- layout: doc-page -title: "Implied Conversions" +title: "Inferable Conversions" --- Inferable conversions are defined by implied instances of the `scala.Conversion` class. @@ -8,13 +8,13 @@ This class is defined in package `scala` as follows: ```scala abstract class Conversion[-T, +U] extends (T => U) ``` -For example, here is an implied conversion from `String` to `Token`: +For example, here is an inferable conversion from `String` to `Token`: ```scala implied for Conversion[String, Token] { def apply(str: String): Token = new KeyWord(str) } ``` -An implied conversion is applied automatically by the compiler in three situations: +An inferable 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`. diff --git a/docs/docs/reference/contextual/derivation.md b/docs/docs/reference/contextual/derivation.md index 2fe1c6f4097c..2c07e8502f21 100644 --- a/docs/docs/reference/contextual/derivation.md +++ b/docs/docs/reference/contextual/derivation.md @@ -47,8 +47,8 @@ These two conditions ensure that the synthesized derived instances for the trait ```scala def derived[T] with Generic[T] = ... ``` -That is, the `derived` method takes an inferable parameter of type `Generic` that determines the _shape_ of the deriving type `T` and it computes the typeclass implementation according to that shape. A `Generic` instance is generated automatically -for any types that derives a typeclass that needs it. One can also derive `Generic` alone, which means a `Generic` instance is generated without any other type class instances. E.g.: +That is, the `derived` method takes an inferable parameter of type `Generic` that determines the _shape_ of the deriving type `T` and it computes the typeclass implementation according to that shape. An implied `Generic` instance is generated automatically for any type that derives a typeclass with a `derived` +method that refers to `Generic`. One can also derive `Generic` alone, which means a `Generic` instance is generated without any other type class instances. E.g.: ```scala sealed trait ParseResult[T] derives Generic ``` @@ -97,7 +97,7 @@ Cases[( Note that an empty element tuple is represented as type `Unit`. A single-element tuple is represented as `T *: Unit` since there is no direct syntax for such tuples: `(T)` is just `T` in parentheses, not a tuple. -### The Generic TypeClass +### The Generic Typeclass For every class `C[T_1,...,T_n]` with a `derives` clause, the compiler generates in the companion object of `C` an implied instance of `Generic[C[T_1,...,T_n]]` that follows the outline below: diff --git a/docs/docs/reference/contextual/extension-methods.md b/docs/docs/reference/contextual/extension-methods.md index ef225df602af..c38c0c1ba52f 100644 --- a/docs/docs/reference/contextual/extension-methods.md +++ b/docs/docs/reference/contextual/extension-methods.md @@ -78,23 +78,23 @@ and where `T` is the expected type. The following two rewritings are tried in or So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided `circle` has type `Circle` and `CircleOps` is an eligible implied instance (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). -## Instances for Extension Methods +### Implied Instances for Extension Methods -Instances that define extension methods can also be defined without an `of` clause. E.g., +Implied instances that define extension methods can also be defined without an `of` clause. E.g., ```scala -instance StringOps { +implied StringOps { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } -instance ListOps { +implied ListOps { def (xs: List[T]) second[T] = xs.tail.head } ``` -If such instances are anonymous (as in the examples above), their name is synthesized from the name +If such implied instances are anonymous (as in the examples above), their name is synthesized from the name of the first defined extension method. ### Operators @@ -102,7 +102,7 @@ of the first defined extension method. The extension method syntax also applies to the definition of operators. In each case the definition syntax mirrors the way the operator is applied. Examples: -``` +```scala def (x: String) < (y: String) = ... def (x: Elem) +: (xs: Seq[Elem]) = ... @@ -110,7 +110,7 @@ Examples: 1 +: List(2, 3) ``` The two definitions above translate to -``` +```scala def < (x: String)(y: String) = ... def +: (xs: Seq[Elem])(x: Elem) = ... ``` @@ -120,23 +120,17 @@ to the implementation of right binding operators as normal methods. ### Generic Extensions -The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible to extend a generic type by adding type parameters to an extension method: +The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible to extend a generic type by adding type parameters to an extension method. Examples: ```scala -def (xs: List[T]) second [T] = xs.tail.head -``` - -or: +def (xs: List[T]) second [T] = + xs.tail.head +def (xs: List[List[T]]) flattened [T] = + xs.foldLeft[List[T]](Nil)(_ ++ _) -```scala -def (xs: List[List[T]]) flattened [T] = xs.foldLeft[List[T]](Nil)(_ ++ _) -``` - -or: - -```scala -def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y) +def (x: T) + [T : Numeric](y: T): T = + implicitly[Numeric[T]].plus(x, y) ``` As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the preceding parameter clause. diff --git a/docs/docs/reference/contextual/implicit-conversions.md b/docs/docs/reference/contextual/implicit-conversions.md deleted file mode 100644 index fe512b53121a..000000000000 --- a/docs/docs/reference/contextual/implicit-conversions.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -layout: doc-page -title: "Implicit Conversions" ---- - -Inferable conversions are defined by implied 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 -implied for Conversion[String, Token] { - def apply(str: String): Token = new KeyWord(str) -} -``` -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 in the implicit scope for a an instance of -`scala.Conversion` that maps an argument of type `T` to type `S`. In the second and third -case, it looks for an instance of `scala.Conversion` 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 -implied int2Integer for Conversion[Int, java.lang.Integer] { - def apply(x: Int) = new java.lang.Integer(x) -} -``` - -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.from(statusCode) - - implied from for Conversion[String, CompletionArg] { - def apply(s: String) = CompletionArg.Error(s) - } - implied from for Conversion[Future[HttpResponse], CompletionArg] { - def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) - } - implied from for Conversion[Future[StatusCode], CompletionArg] { - def apply(code: Future[StatusCode]) = CompletionArg.Status(code) - } - } - 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/inferable-by-name-parameters.md b/docs/docs/reference/contextual/inferable-by-name-parameters.md index 08c91e6519e1..9ebdbe9f5c49 100644 --- a/docs/docs/reference/contextual/inferable-by-name-parameters.md +++ b/docs/docs/reference/contextual/inferable-by-name-parameters.md @@ -3,7 +3,7 @@ layout: doc-page title: "Inferable By-Name Parameters" --- -Call-by-name inferable parameters can be used to avoid a divergent inferred expansion. +Inferable by-name parameters can be used to avoid a divergent inferred expansion. Example: ```scala trait Codec[T] { @@ -36,16 +36,17 @@ The precise steps for constructing an inferable argument for a by-name parameter 1. Create a new implied instance of type `T`: ```scala - implied for T = ??? + implied lv for T = ??? ``` + where `lv` is an arbitrary fresh name. - 1. This instance is not immediately available as candidate for argument inference (making it immediately available would result in a loop in the synthesized computation). But it becomes available in all nested contexts that look again for an inferred argument to a by-name parameter. + 1. This 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 inferred argument to a by-name parameter. - 1. If this search succeeds with expression `E`, and `E` contains references to the implied instance created previously, replace `E` by + 1. If this search succeeds with expression `E`, and `E` contains references to the implied instance `lv`, replace `E` by ```scala - { implied for T = E; lv } + { implied lv for T = E; lv } ``` Otherwise, return `E` unchanged. diff --git a/docs/docs/reference/contextual/inferable-params.md b/docs/docs/reference/contextual/inferable-params.md index 335e7bf8913e..0eba6a74306b 100644 --- a/docs/docs/reference/contextual/inferable-params.md +++ b/docs/docs/reference/contextual/inferable-params.md @@ -94,7 +94,7 @@ The `infer` method is simply defined as the identity function over an inferable ```scala def infer[T] given (x: T) = x ``` -Functions like `infer` that have only inferable parameters are also called implicit _queries_. +Functions like `infer` that have only inferable parameters are also called _context queries_. ## Syntax diff --git a/docs/docs/reference/contextual/query-types-spec.md b/docs/docs/reference/contextual/query-types-spec.md index 001e5637e301..d1ee471ae7df 100644 --- a/docs/docs/reference/contextual/query-types-spec.md +++ b/docs/docs/reference/contextual/query-types-spec.md @@ -1,10 +1,8 @@ --- layout: doc-page -title: "Query Types - More Details" +title: "Context Query Types - More Details" --- -Initial implementation in (#1775)[https://github.com/lampepfl/dotty/pull/1775]. - ## Syntax Type ::= ... @@ -12,12 +10,12 @@ Initial implementation in (#1775)[https://github.com/lampepfl/dotty/pull/1775]. Expr ::= ... | `given' FunParams `=>' Expr -Query types associate to the right, e.g. +Context query types associate to the right, e.g. `given S => given T => U` is the same as `given S => (given T => U)`. ## Implementation -Query types are shorthands for class types that define `apply` +Context query types are shorthands for class types that define `apply` methods with inferable 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`: @@ -27,10 +25,10 @@ trait ImplicitFunctionN[-T1 , ... , -TN, +R] { def apply given (x1: T1 , ... , xN: TN): R } ``` -Query types erase to normal function types, so these classes are +Context query types erase to normal function types, so these classes are generated on the fly for typechecking, but not realized in actual code. -Query literals `given (x1: T1, ..., xn: Tn) => e` map +Context query literals `given (x1: T1, ..., xn: Tn) => e` map inferable parameters `xi` of types `Ti` to a result given by expression `e`. The scope of each implicit parameter `xi` is `e`. The parameters must have pairwise distinct names. @@ -55,10 +53,9 @@ abbreviated to `given x => e`. An inferable 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 the Scala 2.12 is subsumed by query types and should -be removed. +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 query types and should be removed. Query literals `given (x1: T1, ..., xn: Tn) => e` are automatically created for any expression `e` whose expected type is @@ -66,7 +63,7 @@ automatically created for any expression `e` whose expected type is itself a query literal. This is analogous to the automatic insertion of `scala.Function0` around expressions in by-name argument position. -Query types generalize to `N > 22` in the same way that function types do, see [the corresponding +Context query types generalize to `N > 22` in the same way that function types do, see [the corresponding documentation](https://dotty.epfl.ch/docs/reference/dropped-features/limit22.html). ## Examples @@ -79,4 +76,4 @@ Gist](https://gist.github.com/OlivierBlanvillain/234d3927fe9e9c6fba074b53a7bd9 ### Type Checking -After desugaring no additional typing rules are required for query types. +After desugaring no additional typing rules are required for context query types. diff --git a/docs/docs/reference/contextual/query-types.md b/docs/docs/reference/contextual/query-types.md index 1830eb41d731..0df2b20b9cba 100644 --- a/docs/docs/reference/contextual/query-types.md +++ b/docs/docs/reference/contextual/query-types.md @@ -1,14 +1,15 @@ --- layout: doc-page -title: "First Class Queries" +title: "Context Queries" --- -In the context of inference, _queries_ are functions with inferable parameters. -_Query types_ are the types of first-class queries. Example: +_Context queries_ are functions with inferable parameters. +_Context query types_ are the types of first-class context queries. +Here is an example for a context query type: ```scala type Contextual[T] = given Context => T ``` -A value of query type is applied to inferred arguments, in +A value of context query type is applied to inferred arguments, in the same way a method with inferable parameters is applied. For instance: ```scala implied ctx for Context = ... @@ -18,9 +19,9 @@ the same way a method with inferable parameters is applied. For instance: f(2) given ctx // explicit argument f(2) // argument is inferred ``` -Conversely, if the expected type of an expression `E` is a query +Conversely, if the expected type of an expression `E` is a context query type `given (T_1, ..., T_n) => U` and `E` is not already a -query literal, `E` is converted to a query literal by rewriting to +context query literal, `E` is converted to a context query literal by rewriting to ```scala given (x_1: T1, ..., x_n: Tn) => E ``` @@ -31,7 +32,7 @@ are available as implied instances in `E`. Like query types, query literals are written with a `given` prefix. They differ from normal function literals in two ways: 1. Their parameters are inferable. - 2. Their types are query types. + 2. Their types are context query types. For example, continuing with the previous definitions, ```scala @@ -45,7 +46,7 @@ For example, continuing with the previous definitions, ``` ### Example: Builder Pattern -Query types have considerable expressive power. For +Context query 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 @@ -111,7 +112,7 @@ With that setup, the table construction code above compiles and expands to: ``` ### Example: Postconditions -As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, query types, and extension methods to provide a zero-overhead abstraction. +As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, context query types, and extension methods to provide a zero-overhead abstraction. ```scala object PostConditions { @@ -136,7 +137,7 @@ object Test { val s = List(1, 2, 3).sum.ensuring(result == 6) } ``` -**Explanations**: We use a query type `given WrappedResult[T] => Boolean` +**Explanations**: We use a context query type `given WrappedResult[T] => Boolean` as the type of the condition of `ensuring`. An argument to `ensuring` such as `(result == 6)` will therefore have an implied instance of type `WrappedResult[T]` in scope to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure diff --git a/docs/docs/reference/contextual/relationship-implicits.md b/docs/docs/reference/contextual/relationship-implicits.md index cb3a67ea0d53..f918c20b2fcb 100644 --- a/docs/docs/reference/contextual/relationship-implicits.md +++ b/docs/docs/reference/contextual/relationship-implicits.md @@ -3,39 +3,40 @@ 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. +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 Contextual Abstraction with Implicits ### Implied Instance Definitions Implied instance definitions can be mapped to combinations of implicit objects, classes and implicit methods. -Instance definitions without parameters are mapped to implicit objects. E.g., -```scala - implied IntOrd for Ord[Int] { ... } -``` -maps to -```scala - implicit object IntOrd extends Ord[Int] { ... } -``` -Parameterized instance definitions are mapped to combinations of classes and implicit methods. E.g., -```scala - implied ListOrd[T] given (ord: Ord[T]) for 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] -``` -Implied alias instances map to implicit methods. E.g., -```scala - implied ctx for ExecutionContext = ... -``` -maps to -```scala - final implicit def ctx: ExecutionContext = ... -``` + + 1. Instance definitions without parameters are mapped to implicit objects. E.g., + ```scala + implied IntOrd for Ord[Int] { ... } + ``` + maps to + ```scala + implicit object IntOrd extends Ord[Int] { ... } + ``` + 2. Parameterized instance definitions are mapped to combinations of classes and implicit methods. E.g., + ```scala + implied ListOrd[T] given (ord: Ord[T]) for 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. Implied alias instances map to implicit methods. E.g., + ```scala + implied ctx for ExecutionContext = ... + ``` + maps to + ```scala + final implicit def ctx: ExecutionContext = ... + ``` + ### Anonymous Implied Instances Anonymous 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` instances above were left out, the following names would be synthesized instead: @@ -80,7 +81,7 @@ The `infer` method corresponds to `implicitly` in Scala 2. Context bounds are the same in both language versions. They expand to the respective forms of implicit parameters. -Note: To make migration simpler, context bounds in Dotty map for a limited time to old-style implicit parameters for which arguments can be passed either with `given` or +**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. ### Extension Methods @@ -109,11 +110,12 @@ 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 in Dotty +## Simulating Scala 2 Implicits in Dotty ### Implicit Conversions -Implicit conversion methods in Scala 2 can be expressed as instances of the `scala.Conversion` class in Dotty. E.g. instead of +Implicit conversion methods in Scala 2 can be expressed as implied instances +of the `scala.Conversion` class in Dotty. E.g. instead of ```scala implicit def stringToToken(str: String): Token = new Keyword(str) ``` @@ -143,11 +145,18 @@ can be expressed in Dotty as ### Abstract Implicits An abstract implicit `val` or `def` in Scala 2 can be expressed in Dotty using a regular abstract definition and an instance alias. E.g., Scala 2's -``` +```scala implicit def symDeco: SymDeco ``` can be expressed in Dotty as -``` +```scala def symDeco: SymDeco implied for 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/replacing-implicits.md b/docs/docs/reference/contextual/replacing-implicits.md deleted file mode 100644 index 8f80b77a17a8..000000000000 --- a/docs/docs/reference/contextual/replacing-implicits.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -layout: doc-page -title: "Replacing Implicits" ---- - -The previous pages describe a new, high-level syntax for implicit definitions, parameters, function literals, and function types. -These idioms can by-and-large be mapped to existing implicits. The only exception concerns context parameters which give genuinely more freedom in the way parameters can be organized. The new idioms are preferable to existing implicits since they are both more concise and better behaved. The better expressiveness comes at a price, however, since it leaves us with both the new and the old way to express implicits. This page discusses what would be needed to get rid of all existing uses of `implicit` as a modifier. - -The current Dotty implementation implements the new concepts described on this page (alias instances and the summon method), but it does not remove any of the old-style implicit constructs. It cannot do this since support -for old-style implicits is an essential part of the common language subset of Scala 2 and Scala 3.0. Any deprecation and subsequent removal of these constructs would have to come later, in a version following 3.0. The `implicit` modifier can be removed from the language at the end of this development, if it happens. - -## Add: Alias Instances - -To replace implicit vals and defs (both abstract and concrete), we need one way to -"lift" an existing value to become an implicit instance for a type. This is achieved -by an alias instance, which creates an instance that is equal to some expression. -```scala -implicit ctx for ExecutionContext = currentThreadPool().context -``` -Here, we create an implicit `ctx` of type `ExecutionContext` that resolves to the -right hand side `currentThreadPool().context`. Each time an implicit of `ExecutionContext` -is demanded, the result of evaluating the right-hand side expression is returned. The definition is equivalent to the following implicit definition in Scala 2: -```scala -final implicit def ctx: ExecutionContext = currentThreadPool().context -``` -Implicit aliases may be anonymous, e.g. -```scala -implicit for Position = enclosingTree.position -``` -An implicit alias can have type and context parameters just like any other implicit definition, but it can only implement a single type. - -## Drop: Implicit Conversions - -Implicit conversions using the `implicit def` syntax are no longer needed, since they -can be expressed as instances of the `scala.Conversion` class. - -## Drop: Implicit Classes - -Most use cases of implicit classes are already covered by extension methods. For the others, one can always fall back to a pair of a regular class and a `Conversion` instance. - -## Drop: Implicit As A Modifier - - - Old-style implicit parameters are replaced by `given` parameters. - - Implicit function types `implicit T => U` are written `given T => U` - - Implicit closures `implicit x => e` are written `given x => e` - - All remaining implicit `val` and `def` definition are replaced by normal - `val` or `def` definitions and implicit aliases.= - -## Syntax - -The syntax changes for this page are summarized as follows: -``` -InstanceBody ::= ... - | ‘for’ Type ‘=’ Expr -``` -In addition, the `implicit` modifier is removed together with all [productions]((http://dotty.epfl.ch/docs/internals/syntax.html) that reference it. diff --git a/docs/docs/reference/derivation.md b/docs/docs/reference/derivation.md deleted file mode 100644 index 7439be868cc4..000000000000 --- a/docs/docs/reference/derivation.md +++ /dev/null @@ -1,392 +0,0 @@ ---- -layout: doc-page -title: Typeclass Derivation ---- - -Typeclass derivation is a way to generate instances of certain type classes automatically or with minimal code hints. A type class in this sense is any trait or class with a type parameter that describes the type being operated on. Commonly used examples are `Eq`, `Ordering`, `Show`, or `Pickling`. Example: -```scala -enum Tree[T] derives Eq, Ordering, Pickling { - case Branch(left: Tree[T], right: Tree[T]) - case Leaf(elem: T) -} -``` -The `derives` clause generates implicit instances of the `Eq`, `Ordering`, and `Pickling` traits in the companion object `Tree`: -```scala -implicit def derived$Eq [T: Eq]: Eq[Tree[T]] = Eq.derived -implicit def derived$Ordering [T: Ordering]: Ordering[Tree[T]] = Ordering.derived -implicit def derived$Pickling [T: Pickling]: Pickling[Tree[T]] = Pickling.derived -``` - -### Deriving Types - -Besides for `enums`, typeclasses can also be derived for other sets of classes and objects that form an algebraic data type. These are: - - - individual case classes or case objects - - sealed classes or traits that have only case classes and case objects as children. - - Examples: - - ```scala -case class Labelled[T](x: T, label: String) derives Eq, Show - -sealed trait Option[T] derives Eq -case class Some[T] extends Option[T] -case object None extends Option[Nothing] -``` - -The generated typeclass instances are placed in the companion objects `Labelled` and `Option`, respectively. - -### Derivable Types - -A trait or class can appear in a `derives` clause if - - - it has a single type parameter, and - - its companion object defines a method named `derived`. - -These two conditions ensure that the synthesized derived instances for the trait are well-formed. The type and implementation of a `derived` method are arbitrary, but typically it has a definition like this: -```scala - def derived[T](implicit ev: Generic[T]) = ... -``` -That is, the `derived` method takes an implicit parameter of type `Generic` that determines the _shape_ of the deriving type `T` and it computes the typeclass implementation according to that shape. Implicit `Generic` instances are generated automatically for all types that have a `derives` clause. One can also derive `Generic` alone, which means a `Generic` instance is generated without any other type class instances. E.g.: -```scala -sealed trait ParseResult[T] derives Generic -``` -This is all a user of typeclass derivation has to know. The rest of this page contains information needed to be able to write a typeclass that can appear in a `derives` clause. In particular, it details the means provided for the implementation of data generic `derived` methods. - -### The Shape Type - -For every class with a `derives` clause, the compiler computes the shape of that class as a type. For instance, here is the shape type for the `Tree[T]` enum: -```scala -Cases[( - Case[Branch[T], (Tree[T], Tree[T])], - Case[Leaf[T], T *: Unit] -)] -``` -Informally, this states that - -> The shape of a `Tree[T]` is one of two cases: Either a `Branch[T]` with two - elements of type `Tree[T]`, or a `Leaf[T]` with a single element of type `T`. - -The type constructors `Cases` and `Case` come from the companion object of a class -`scala.compiletime.Shape`, which is defined in the standard library as follows: -```scala -sealed abstract class Shape - -object Shape { - - /** A sum with alternative types `Alts` */ - case class Cases[Alts <: Tuple] extends Shape - - /** A product type `T` with element types `Elems` */ - case class Case[T, Elems <: Tuple] extends Shape -} -``` - -Here is the shape type for `Labelled[T]`: -```scala -Case[Labelled[T], (T, String)] -``` -And here is the one for `Option[T]`: -```scala -Cases[( - Case[Some[T], T *: Unit], - Case[None.type, Unit] -)] -``` -Note that an empty element tuple is represented as type `Unit`. A single-element tuple -is represented as `T *: Unit` since there is no direct syntax for such tuples: `(T)` is just `T` in parentheses, not a tuple. - -### The Generic TypeClass - -For every class `C[T_1,...,T_n]` with a `derives` clause, the compiler generates in the companion object of `C` an implicit instance of `Generic[C[T_1,...,T_n]]` that follows -the outline below: -```scala -class derived$Generic[T_1, ..., T_n] extends Generic[C[T_1,...,T_n]] { - type Shape = ... - ... -} -implicit def derived$Generic[T_1,...,T_n]]: derived$Generic[T_1,...,T_n]] = - new derived$Generic[T_1,...,T_n]] -``` -where the right hand side of `Shape` is the shape type of `C[T_1,...,T_n]`. -For instance, the definition -```scala -enum Result[+T, +E] derives Logging { - case class Ok[T](result: T) - case class Err[E](err: E) -} -``` -would produce: -```scala -object Result { - import scala.compiletime.Shape._ - - class derived$Generic[T, E] extends Generic[Result[T, E]] { - type Shape = Cases[( - Case[Ok[T], T *: Unit], - Case[Err[E], E *: Unit] - )] - ... - } - implicit def derived$Generic[T, E]: derived$Generic[T, E] = - new derived$Generic[T, E] -} -``` -The `Generic` class is defined in package `scala.reflect`. - -```scala -abstract class Generic[T] { - type Shape <: scala.compiletime.Shape - - /** The mirror corresponding to ADT instance `x` */ - def reflect(x: T): Mirror - - /** The ADT instance corresponding to given `mirror` */ - def reify(mirror: Mirror): T - - /** The companion object of the ADT */ - def common: GenericClass -} -``` -It defines the `Shape` type for the ADT `T`, as well as two methods that map between a -type `T` and a generic representation of `T`, which we call a `Mirror`: -The `reflect` method maps an instance value of the ADT `T` to its mirror whereas -the `reify` method goes the other way. There's also a `common` method that returns -a value of type `GenericClass` which contains information that is the same for all -instances of a class (right now, this consists of the runtime `Class` value and -the names of the cases and their parameters). - -### Mirrors - -A mirror is a generic representation of an instance value of an ADT. `Mirror` objects have three components: - - - `adtClass: GenericClass`: The representation of the ADT class - - `ordinal: Int`: The ordinal number of the case among all cases of the ADT, starting from 0 - - `elems: Product`: The elements of the instance, represented as a `Product`. - - The `Mirror` class is defined in package `scala.reflect` as follows: - -```scala -class Mirror(val adtClass: GenericClass, val ordinal: Int, val elems: Product) { - - /** The `n`'th element of this generic case */ - def apply(n: Int): Any = elems.productElement(n) - - /** The name of the constructor of the case reflected by this mirror */ - def caseLabel: String = adtClass.label(ordinal)(0) - - /** The label of the `n`'th element of the case reflected by this mirror */ - def elementLabel(n: Int): String = adtClass.label(ordinal)(n + 1) -} -``` - -### GenericClass - -Here's the API of `scala.reflect.GenericClass`: - -```scala -class GenericClass(val runtimeClass: Class[_], labelsStr: String) { - - /** A mirror of case with ordinal number `ordinal` and elements as given by `Product` */ - def mirror(ordinal: Int, product: Product): Mirror = - new Mirror(this, ordinal, product) - - /** A mirror with elements given as an array */ - def mirror(ordinal: Int, elems: Array[AnyRef]): Mirror = - mirror(ordinal, new ArrayProduct(elems)) - - /** A mirror with an initial empty array of `numElems` elements, to be filled in. */ - def mirror(ordinal: Int, numElems: Int): Mirror = - mirror(ordinal, new Array[AnyRef](numElems)) - - /** A mirror of a case with no elements */ - def mirror(ordinal: Int): Mirror = - mirror(ordinal, EmptyProduct) - - /** Case and element labels as a two-dimensional array. - * Each row of the array contains a case label, followed by the labels of the elements of that case. - */ - val label: Array[Array[String]] = ... -} -``` - -The class provides four overloaded methods to create mirrors. The first of these is invoked by the `reify` method that maps an ADT instance to its mirror. It simply passes the -instance itself (which is a `Product`) to the second parameter of the mirror. That operation does not involve any copying and is thus quite efficient. The second and third versions of `mirror` are typically invoked by typeclass methods that create instances from mirrors. An example would be an `unpickle` method that first creates an array of elements, then creates -a mirror over that array, and finally uses the `reify` method in `Reflected` to create the ADT instance. The fourth version of `mirror` is used to create mirrors of instances that do not have any elements. - -### How to Write Generic Typeclasses - -Based on the machinery developed so far it becomes possible to define type classes generically. This means that the `derived` method will compute a type class instance for any ADT that has a `Generic` instance, recursively. -The implementation of these methods typically uses three new type-level constructs in Dotty: inline methods, inline matches, and implicit matches. As an example, here is one possible implementation of a generic `Eq` type class, with explanations. Let's assume `Eq` is defined by the following trait: -```scala -trait Eq[T] { - def eql(x: T, y: T): Boolean -} -``` -We need to implement a method `Eq.derived` that produces an instance of `Eq[T]` provided -there exists evidence of type `Generic[T]`. Here's a possible solution: -```scala - inline def derived[T](implicit ev: Generic[T]): Eq[T] = new Eq[T] { - def eql(x: T, y: T): Boolean = { - val mx = ev.reflect(x) // (1) - val my = ev.reflect(y) // (2) - inline erasedValue[ev.Shape] match { - case _: Cases[alts] => - mx.ordinal == my.ordinal && // (3) - eqlCases[alts](mx, my, 0) // [4] - case _: Case[_, elems] => - eqlElems[elems](mx, my, 0) // [5] - } - } - } -``` -The implementation of the inline method `derived` creates an instance of `Eq[T]` and implements its `eql` method. The right-hand side of `eql` mixes compile-time and runtime elements. In the code above, runtime elements are marked with a number in parentheses, i.e -`(1)`, `(2)`, `(3)`. Compile-time calls that expand to runtime code are marked with a number in brackets, i.e. `[4]`, `[5]`. The implementation of `eql` consists of the following steps. - - 1. Map the compared values `x` and `y` to their mirrors using the `reflect` method of the implicitly passed `Generic` evidence `(1)`, `(2)`. - 2. Match at compile-time against the shape of the ADT given in `ev.Shape`. Dotty does not have a construct for matching types directly, but we can emulate it using an `inline` match over an `erasedValue`. Depending on the actual type `ev.Shape`, the match will reduce at compile time to one of its two alternatives. - 3. If `ev.Shape` is of the form `Cases[alts]` for some tuple `alts` of alternative types, the equality test consists of comparing the ordinal values of the two mirrors `(3)` and, if they are equal, comparing the elements of the case indicated by that ordinal value. That second step is performed by code that results from the compile-time expansion of the `eqlCases` call `[4]`. - 4. If `ev.Shape` is of the form `Case[elems]` for some tuple `elems` for element types, the elements of the case are compared by code that results from the compile-time expansion of the `eqlElems` call `[5]`. - -Here is a possible implementation of `eqlCases`: -```scala - inline def eqlCases[Alts <: Tuple](mx: Mirror, my: Mirror, n: Int): Boolean = - inline erasedValue[Alts] match { - case _: (Shape.Case[_, elems] *: alts1) => - if (mx.ordinal == n) // (6) - eqlElems[elems](mx, my, 0) // [7] - else - eqlCases[alts1](mx, my, n + 1) // [8] - case _: Unit => - throw new MatchError(mx.ordinal) // (9) - } -``` -The inline method `eqlCases` takes as type arguments the alternatives of the ADT that remain to be tested. It takes as value arguments mirrors of the two instances `x` and `y` to be compared and an integer `n` that indicates the ordinal number of the case that is tested next. It produces an expression that compares these two values. - -If the list of alternatives `Alts` consists of a case of type `Case[_, elems]`, possibly followed by further cases in `alts1`, we generate the following code: - - 1. Compare the `ordinal` value of `mx` (a runtime value) with the case number `n` (a compile-time value translated to a constant in the generated code) in an if-then-else `(6)`. - 2. In the then-branch of the conditional we have that the `ordinal` value of both mirrors - matches the number of the case with elements `elems`. Proceed by comparing the elements - of the case in code expanded from the `eqlElems` call `[7]`. - 3. In the else-branch of the conditional we have that the present case does not match - the ordinal value of both mirrors. Proceed by trying the remaining cases in `alts1` using - code expanded from the `eqlCases` call `[8]`. - - If the list of alternatives `Alts` is the empty tuple, there are no further cases to check. - This place in the code should not be reachable at runtime. Therefore an appropriate - implementation is by throwing a `MatchError` or some other runtime exception `(9)`. - -The `eqlElems` method compares the elements of two mirrors that are known to have the same -ordinal number, which means they represent the same case of the ADT. Here is a possible -implementation: -```scala - inline def eqlElems[Elems <: Tuple](xs: Mirror, ys: Mirror, n: Int): Boolean = - inline erasedValue[Elems] match { - case _: (elem *: elems1) => - tryEql[elem]( // [12] - xs(n).asInstanceOf[elem], // (10) - ys(n).asInstanceOf[elem]) && // (11) - eqlElems[elems1](xs, ys, n + 1) // [13] - case _: Unit => - true // (14) - } -``` -`eqlElems` takes as arguments the two mirrors of the elements to compare and a compile-time index `n`, indicating the index of the next element to test. It is defined in terms of another compile-time match, this time over the tuple type `Elems` of all element types that remain to be tested. If that type is -non-empty, say of form `elem *: elems1`, the following code is produced: - - 1. Access the `n`'th elements of both mirrors and cast them to the current element type `elem` - `(10)`, `(11)`. Note that because of the way runtime reflection mirrors compile-time `Shape` types, the casts are guaranteed to succeed. - 2. Compare the element values using code expanded by the `tryEql` call `[12]`. - 3. "And" the result with code that compares the remaining elements using a recursive call - to `eqlElems` `[13]`. - - If type `Elems` is empty, there are no more elements to be compared, so the comparison's result is `true`. `(14)` - - Since `eqlElems` is an inline method, its recursive calls are unrolled. The end result is a conjunction `test_1 && ... && test_n && true` of test expressions produced by the `tryEql` calls. - -The last, and in a sense most interesting part of the derivation is the comparison of a pair of element values in `tryEql`. Here is the definition of this method: -```scala - inline def tryEql[T](x: T, y: T) = implicit match { - case ev: Eq[T] => - ev.eql(x, y) // (15) - case _ => - error("No `Eq` instance was found for $T") - } -``` -`tryEql` is an inline method that takes an element type `T` and two element values of that type as arguments. It is defined using an `inline match` that tries to find an implicit instance of `Eq[T]`. If an instance `ev` is found, it proceeds by comparing the arguments using `ev.eql`. On the other hand, if no instance is found -this signals a compilation error: the user tried a generic derivation of `Eq` for a class with an element type that does not support an `Eq` instance itself. The error is signaled by -calling the `error` method defined in `scala.compiletime`. - -**Note:** At the moment our error diagnostics for metaprogramming does not support yet interpolated string arguments for the `scala.compiletime.error` method that is called in the second case above. As an alternative, one can simply leave off the second case, then a missing typeclass would result in a "failure to reduce match" error. - -**Example:** Here is a slightly polished and compacted version of the code that's generated by inline expansion for the derived `Eq` instance of class `Tree`. - -```scala -implicit def derived$Eq[T](implicit elemEq: Eq[T]): Eq[Tree[T]] = new Eq[Tree[T]] { - def eql(x: Tree[T], y: Tree[T]): Boolean = { - val ev = implicitly[Generic[Tree[T]]] - val mx = ev.reflect(x) - val my = ev.reflect(y) - mx.ordinal == my.ordinal && { - if (mx.ordinal == 0) { - derived$Eq.eql(mx(0).asInstanceOf[Tree[T]], my(0).asInstanceOf[Tree[T]]) && - derived$Eq.eql(mx(1).asInstanceOf[Tree[T]], my(1).asInstanceOf[Tree[T]]) - } - else if (mx.ordinal == 1) { - elemEq.eql(mx(0).asInstanceOf[T], my(0).asInstanceOf[T]) - } - else throw new MatchError(mx.ordinal) - } - } -} -``` - -One important difference between this approach and Scala-2 typeclass derivation frameworks such as Shapeless or Magnolia is that no automatic attempt is made to generate typeclass instances of elements recursively using the generic derivation framework. There must be an implicit instance of `Eq[T]` (which can of course be produced in turn using `Eq.derived`), or the compilation will fail. The advantage of this more restrictive approach to typeclass derivation is that it avoids uncontrolled transitive typeclass derivation by design. This keeps code sizes smaller, compile times lower, and is generally more predictable. - -### Derived Instances Elsewhere - -Sometimes one would like to derive a typeclass 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 with the `derived` method of the typeclass as right-hand side. E.g, to implement `Ordering` for `Option`, define: -```scala -implicit def myOptionOrdering[T: Ordering]: Ordering[Option[T]] = Ordering.derived -``` -Usually, the `Ordering.derived` clause has an implicit parameter of type -`Generic[Option[T]]`. Since the `Option` trait has a `derives` clause, -the necessary implicit instance is already present in the companion object of `Option`. -If the ADT in question does not have a `derives` clause, an implicit `Generic` instance -would still be synthesized by the compiler at the point where `derived` is called. -This is similar to the situation with type tags or class tags: If no implicit instance -is found, the compiler will synthesize one. - -### Syntax - -``` -Template ::= InheritClauses [TemplateBody] -EnumDef ::= id ClassConstr InheritClauses EnumBody -InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] -ConstrApps ::= ConstrApp {‘with’ ConstrApp} - | ConstrApp {‘,’ ConstrApp} -``` - -### Discussion - -The typeclass derivation framework is quite small and low-level. There are essentially -two pieces of infrastructure in the compiler-generated `Generic` instances: - - - a type representing the shape of an ADT, - - a way to map between ADT instances and generic mirrors. - -Generic mirrors make use of the already existing `Product` infrastructure for case -classes, which means they are efficient and their generation requires not much code. - -Generic mirrors can be so simple because, just like `Product`s, they are weakly -typed. On the other hand, this means that code for generic typeclasses 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 typeclasses -are correctly written these casts will never fail. - -It could make sense to explore a higher-level framework that encapsulates all casts -in the framework. This could give more guidance to the typeclass implementer. -It also seems quite possible to put such a framework on top of the lower-level -mechanisms presented here. diff --git a/docs/docs/reference/dropped-features/limit22.md b/docs/docs/reference/dropped-features/limit22.md index 7a7cbf931bf9..285dcb543629 100644 --- a/docs/docs/reference/dropped-features/limit22.md +++ b/docs/docs/reference/dropped-features/limit22.md @@ -3,11 +3,11 @@ layout: doc-page title: Dropped: Limit 22 --- -The limit of 22 for the maximal number of parameters of function types -has been dropped. Functions can now have an arbitrary number of +The limits of 22 for the maximal number of parameters of function types +and the maximal number of fields in tuple types have been dropped. + +Functions can now have an arbitrary number of parameters. Functions beyond Function22 are represented with a new trait `scala.FunctionXXL`. -The limit of 22 for the size of tuples is about to be dropped. Tuples -will in the future be represented by an HList-like structure which can -be arbitrarily large. +Tuples can also have an arbitrary number of fields. Furthermore, they support generic operation such as concatenation and indexing. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index bea5fc5ed4e6..bea0d5303028 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -52,7 +52,7 @@ sidebar: url: docs/reference/contextual/typeclasses.html - title: Typeclass Derivation url: docs/reference/derivation.html - - title: Query Types + - title: Context Queries url: docs/reference/contextual/query-types.html - title: Implied Conversions url: docs/reference/contextual/conversions.html From 53f00ae495c518f623a95fe430b0b57bfa50a4fb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 1 Feb 2019 08:31:00 +0100 Subject: [PATCH 13/16] Implied Conversion -> Implicit Conversion Implied felt forced in this context --- docs/docs/reference/contextual/conversions.md | 8 ++++---- docs/docs/reference/contextual/query-types.md | 4 ++-- docs/docs/reference/contextual/relationship-implicits.md | 2 +- docs/sidebar.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/docs/reference/contextual/conversions.md b/docs/docs/reference/contextual/conversions.md index 4f169670982a..072eab53c2b9 100644 --- a/docs/docs/reference/contextual/conversions.md +++ b/docs/docs/reference/contextual/conversions.md @@ -1,20 +1,20 @@ --- layout: doc-page -title: "Inferable Conversions" +title: "Implicit Conversions" --- -Inferable conversions are defined by implied instances of the `scala.Conversion` class. +Implicit conversions are defined by implied 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 inferable conversion from `String` to `Token`: +For example, here is an implicit conversion from `String` to `Token`: ```scala implied for Conversion[String, Token] { def apply(str: String): Token = new KeyWord(str) } ``` -An inferable conversion is applied automatically by the compiler in three situations: +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`. diff --git a/docs/docs/reference/contextual/query-types.md b/docs/docs/reference/contextual/query-types.md index 0df2b20b9cba..43929f17c601 100644 --- a/docs/docs/reference/contextual/query-types.md +++ b/docs/docs/reference/contextual/query-types.md @@ -3,7 +3,7 @@ layout: doc-page title: "Context Queries" --- -_Context queries_ are functions with inferable parameters. +_Context queries_ are functions with (only) inferable parameters. _Context query types_ are the types of first-class context queries. Here is an example for a context query type: ```scala @@ -26,7 +26,7 @@ context query literal, `E` is converted to a context query literal by rewriting 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` +before the expression `E` is typechecked, which means that `x_1`, ..., `x_n` are available as implied instances in `E`. Like query types, query literals are written with a `given` prefix. They differ from normal function literals in two ways: diff --git a/docs/docs/reference/contextual/relationship-implicits.md b/docs/docs/reference/contextual/relationship-implicits.md index f918c20b2fcb..682ae9ddcd15 100644 --- a/docs/docs/reference/contextual/relationship-implicits.md +++ b/docs/docs/reference/contextual/relationship-implicits.md @@ -144,7 +144,7 @@ can be expressed in Dotty as ### Abstract Implicits -An abstract implicit `val` or `def` in Scala 2 can be expressed in Dotty using a regular abstract definition and an instance alias. E.g., Scala 2's +An abstract implicit `val` or `def` in Scala 2 can be expressed in Dotty using a regular abstract definition and an implied alias. E.g., Scala 2's ```scala implicit def symDeco: SymDeco ``` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index bea0d5303028..c0d67dab2276 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -54,7 +54,7 @@ sidebar: url: docs/reference/derivation.html - title: Context Queries url: docs/reference/contextual/query-types.html - - title: Implied Conversions + - title: Implicit Conversions url: docs/reference/contextual/conversions.html - title: Inferable By-Name Parameters url: docs/reference/contextual/inferable-by-name-parameters.html From a1ffafcc24099bc55eddbae38ad02e0cb075b3db Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 1 Feb 2019 09:12:47 +0100 Subject: [PATCH 14/16] Rename internals to new terminologu Token: INSTANCE -> IMPLIED Flag: Contextual -> Given --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 4 ++-- compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 6 +++--- compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- compiler/src/dotty/tools/dotc/core/Flags.scala | 4 ++-- .../dotty/tools/dotc/core/tasty/TastyFormat.scala | 8 ++++---- .../dotty/tools/dotc/core/tasty/TreePickler.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- .../src/dotty/tools/dotc/parsing/Parsers.scala | 14 +++++++------- compiler/src/dotty/tools/dotc/parsing/Tokens.scala | 6 +++--- .../dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- .../src/dotty/tools/dotc/typer/EtaExpansion.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 6 +++--- 14 files changed, 32 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 695795c878db..4b4e1784a85d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1086,8 +1086,8 @@ object desugar { } def makeContextualFunction(formals: List[Type], body: Tree)(implicit ctx: Context): Tree = { - val params = makeImplicitParameters(formals.map(TypeTree), Contextual) - new FunctionWithMods(params, body, Modifiers(Implicit | Contextual)) + val params = makeImplicitParameters(formals.map(TypeTree), Given) + new FunctionWithMods(params, body, Modifiers(Implicit | Given)) } /** Add annotation to tree: diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index c10adfbc5cbf..e1b5a5c8997a 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -331,13 +331,13 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] /** Is `tree` an implicit function or closure, possibly nested in a block? */ def isContextualClosure(tree: Tree)(implicit ctx: Context): Boolean = unsplice(tree) match { - case tree: FunctionWithMods => tree.mods.is(Contextual) - case Function((param: untpd.ValDef) :: _, _) => param.mods.is(Contextual) + case tree: FunctionWithMods => tree.mods.is(Given) + case Function((param: untpd.ValDef) :: _, _) => param.mods.is(Given) case Closure(_, meth, _) => true case Block(Nil, expr) => isContextualClosure(expr) case Block(DefDef(nme.ANON_FUN, _, params :: _, _, _) :: Nil, cl: Closure) => params match { - case param :: _ => param.mods.is(Contextual) + case param :: _ => param.mods.is(Given) case Nil => cl.tpt.eq(untpd.ContextualEmptyTree) || defn.isImplicitFunctionType(cl.tpt.typeOpt) } case _ => false diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index c180f514822e..e3d1127a08db 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -234,7 +234,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def valueParam(name: TermName, origInfo: Type): TermSymbol = { val maybeImplicit = - if (tp.isContextual) Implicit | Contextual + if (tp.isContextual) Implicit | Given else if (tp.isImplicitMethod) Implicit else EmptyFlags val maybeErased = if (tp.isErasedMethod) Erased else EmptyFlags diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 61098a28d6e1..a074281674c2 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -132,7 +132,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Implicit()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.ImplicitCommon) - case class Given()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.ImplicitCommon | Flags.Contextual) + case class Given()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.ImplicitCommon | Flags.Given) case class Erased()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Erased) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index f7bad7265047..0d6e42b3318a 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -347,8 +347,8 @@ object Flags { /** An extension method */ final val Extension = termFlag(28, "") - /** A contextual (with) parameter */ - final val Contextual = commonFlag(29, "") + /** An inferable (`given`) parameter */ + final val Given = commonFlag(29, "given") /** Symbol is defined by a Java class */ final val JavaDefined: FlagSet = commonFlag(30, "") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 9961d3b688ee..b794555fed5f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -203,7 +203,7 @@ Standard-Section: "ASTs" TopLevelStat* DEFAULTparameterized // Method with default parameters STABLE // Method that is assumed to be stable EXTENSION // An extension method - CONTEXTUAL // new style implicit parameters, introduced with `with` + GIVEN // new style implicit parameters, introduced with `given` PARAMsetter // A setter without a body named `x_=` where `x` is pickled as a PARAM Annotation @@ -323,7 +323,7 @@ object TastyFormat { final val ERASED = 34 final val OPAQUE = 35 final val EXTENSION = 36 - final val CONTEXTUAL = 37 + final val GIVEN = 37 final val PARAMsetter = 38 // Cat. 2: tag Nat @@ -497,7 +497,7 @@ object TastyFormat { | DEFAULTparameterized | STABLE | EXTENSION - | CONTEXTUAL + | GIVEN | PARAMsetter | ANNOTATION | PRIVATEqualified @@ -558,7 +558,7 @@ object TastyFormat { case DEFAULTparameterized => "DEFAULTparameterized" case STABLE => "STABLE" case EXTENSION => "EXTENSION" - case CONTEXTUAL => "CONTEXTUAL" + case GIVEN => "GIVEN" case PARAMsetter => "PARAMsetter" case SHAREDterm => "SHAREDterm" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index e0eaa47609f9..414646749b96 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -655,7 +655,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is DefaultParameterized) writeByte(DEFAULTparameterized) if (flags is StableRealizable) writeByte(STABLE) if (flags is Extension) writeByte(EXTENSION) - if (flags is Contextual) writeByte(CONTEXTUAL) + if (flags is Given) writeByte(GIVEN) if (flags is ParamAccessor) writeByte(PARAMsetter) assert(!(flags is Label)) } else { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index e442f9b00738..bdea7dcbe584 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -632,7 +632,7 @@ class TreeUnpickler(reader: TastyReader, case DEFAULTparameterized => addFlag(DefaultParameterized) case STABLE => addFlag(StableRealizable) case EXTENSION => addFlag(Extension) - case CONTEXTUAL => addFlag(Contextual) + case GIVEN => addFlag(Given) case PARAMsetter => addFlag(ParamAccessor) case PRIVATEqualified => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 1d7e348e4beb..3ba8d0ebeed6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -783,7 +783,7 @@ object Parsers { def functionRest(params: List[Tree]): Tree = atSpan(start, accept(ARROW)) { val t = typ() - if (imods.is(Implicit | Contextual | Erased)) new FunctionWithMods(params, t, imods) + if (imods.is(Implicit | Given | Erased)) new FunctionWithMods(params, t, imods) else Function(params, t) } def funArgTypesRest(first: Tree, following: () => Tree) = { @@ -2090,7 +2090,7 @@ object Parsers { // begin paramClause inParens { - val isContextual = impliedMods.is(Contextual) + val isContextual = impliedMods.is(Given) if (in.token == RPAREN && !prefix && !isContextual) Nil else { def funArgMods(mods: Modifiers): Modifiers = @@ -2123,10 +2123,10 @@ object Parsers { val initialMods = if (in.token == GIVEN) { in.nextToken() - Modifiers(Contextual | Implicit) + Modifiers(Given | Implicit) } else EmptyModifiers - val isContextual = initialMods.is(Contextual) + val isContextual = initialMods.is(Given) newLineOptWhenFollowedBy(LPAREN) if (in.token == LPAREN) { if (ofInstance && !isContextual) @@ -2137,14 +2137,14 @@ object Parsers { firstClause = firstClause, initialMods = initialMods) val lastClause = - params.nonEmpty && params.head.mods.flags.is(Implicit, butNot = Contextual) + params.nonEmpty && params.head.mods.flags.is(Implicit, butNot = Given) params :: (if (lastClause) Nil else recur(firstClause = false, nparams + params.length)) } else if (isContextual) { val tps = commaSeparated(refinedType) var counter = nparams def nextIdx = { counter += 1; counter } - val params = tps.map(makeSyntheticParameter(nextIdx, _, Contextual | Implicit)) + val params = tps.map(makeSyntheticParameter(nextIdx, _, Given | Implicit)) params :: recur(firstClause = false, nparams + params.length) } else Nil @@ -2438,7 +2438,7 @@ object Parsers { objectDef(start, posMods(start, mods | Case | Module)) case ENUM => enumDef(start, mods, atSpan(in.skipToken()) { Mod.Enum() }) - case INSTANCE => + case IMPLIED => instanceDef(start, mods, atSpan(in.skipToken()) { Mod.Instance() }) case _ => syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition()) diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 8ad818487dff..20dcf1b7e152 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -178,7 +178,7 @@ object Tokens extends TokensCommon { final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate final val ENUM = 62; enter(ENUM, "enum") final val ERASED = 63; enter(ERASED, "erased") - final val INSTANCE = 64; enter(INSTANCE, "implied") + final val IMPLIED = 64; enter(IMPLIED, "implied") final val GIVEN = 65; enter(GIVEN, "given") /** special symbols */ @@ -223,7 +223,7 @@ object Tokens extends TokensCommon { final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) - final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE, INSTANCE) + final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE, IMPLIED) final val defIntroTokens: TokenSet = templateIntroTokens | dclIntroTokens @@ -251,7 +251,7 @@ object Tokens extends TokensCommon { final val numericLitTokens: TokenSet = BitSet(INTLIT, LONGLIT, FLOATLIT, DOUBLELIT) - final val scala3keywords = BitSet(ENUM, ERASED, GIVEN, INSTANCE) + final val scala3keywords = BitSet(ENUM, ERASED, GIVEN, IMPLIED) final val softModifierNames = Set(nme.inline, nme.opaque) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 8c754febb208..6ad13a74f973 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -520,7 +520,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def argToText(arg: Tree) = arg match { case arg @ ValDef(name, tpt, _) => val implicitText = - if ((arg.mods is Contextual)) { contextual = true; "" } + if ((arg.mods is Given)) { contextual = true; "" } else if ((arg.mods is Implicit) && !implicitSeen) { implicitSeen = true; keywordStr("implicit ") } else "" implicitText ~ toText(name) ~ optAscription(tpt) diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index 70e89d5d7ab7..f217388697f2 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -210,7 +210,7 @@ object EtaExpansion extends LiftImpure { if (isLastApplication && mt.paramInfos.length == xarity) mt.paramInfos map (_ => TypeTree()) else mt.paramInfos map TypeTree var paramFlag = Synthetic | Param - if (mt.isContextual) paramFlag |= Contextual + if (mt.isContextual) paramFlag |= Given if (mt.isImplicitMethod) paramFlag |= Implicit val params = (mt.paramNames, paramTypes).zipped.map((name, tpe) => ValDef(name, tpe, EmptyTree).withFlags(paramFlag).withSpan(tree.span.startPos)) @@ -221,7 +221,7 @@ object EtaExpansion extends LiftImpure { if (mt.isContextual) body.pushAttachment(WithApply, ()) if (!isLastApplication) body = PostfixOp(body, Ident(nme.WILDCARD)) val fn = - if (mt.isContextual) new untpd.FunctionWithMods(params, body, Modifiers(Implicit | Contextual)) + if (mt.isContextual) new untpd.FunctionWithMods(params, body, Modifiers(Implicit | Given)) else if (mt.isImplicitMethod) new untpd.FunctionWithMods(params, body, Modifiers(Implicit)) else untpd.Function(params, body) if (defs.nonEmpty) untpd.Block(defs.toList map (untpd.TypedSplice(_)), fn) else fn diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 092969fe43ee..2b22f6d3c7db 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -136,7 +136,7 @@ trait NamerContextOps { this: Context => (valueParamss :\ resultType) { (params, resultType) => val (isImplicit, isErased, isContextual) = if (params.isEmpty) (false, false, false) - else (params.head is Implicit, params.head is Erased, params.head.is(Contextual)) + else (params.head is Implicit, params.head is Erased, params.head.is(Given)) val make = MethodType.maker(isJava = isJava, isImplicit = isImplicit, isErased = isErased, isContextual = isContextual) if (isJava) for (param <- params) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 80e280defd64..e72d8dcc471c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -791,7 +791,7 @@ class Typer extends Namer } val funCls = defn.FunctionClass(args.length, - isContextual = funFlags.is(Contextual), isErased = funFlags.is(Erased)) + isContextual = funFlags.is(Given), isErased = funFlags.is(Erased)) /** Typechecks dependent function type with given parameters `params` */ def typedDependent(params: List[ValDef])(implicit ctx: Context): Tree = { @@ -801,7 +801,7 @@ class Typer extends Namer params1.foreach(_.symbol.setFlag(funFlags)) val resultTpt = typed(body) val companion = MethodType.maker( - isContextual = funFlags.is(Contextual), isErased = funFlags.is(Erased)) + isContextual = funFlags.is(Given), isErased = funFlags.is(Erased)) val mt = companion.fromSymbols(params1.map(_.symbol), resultTpt.tpe) if (mt.isParamDependent) ctx.error(i"$mt is an illegal function type because it has inter-parameter dependencies", tree.sourcePos) @@ -829,7 +829,7 @@ class Typer extends Namer val untpd.Function(params: List[untpd.ValDef] @unchecked, body) = tree val isContextual = tree match { - case tree: untpd.FunctionWithMods => tree.mods.is(Contextual) + case tree: untpd.FunctionWithMods => tree.mods.is(Given) case _ => false } From 5f3775511d264e863df704f4c7784424078b652e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 1 Feb 2019 13:10:29 +0100 Subject: [PATCH 15/16] Fix typo and example code --- docs/docs/reference/contextual/conversions.md | 2 +- docs/docs/reference/contextual/instance-defs.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/contextual/conversions.md b/docs/docs/reference/contextual/conversions.md index 072eab53c2b9..e838964a5144 100644 --- a/docs/docs/reference/contextual/conversions.md +++ b/docs/docs/reference/contextual/conversions.md @@ -18,7 +18,7 @@ An implicit conversion is applied automatically by the compiler in three situati 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 +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 in the implied scope for a an instance of diff --git a/docs/docs/reference/contextual/instance-defs.md b/docs/docs/reference/contextual/instance-defs.md index bcf919c02961..80ea0deadccd 100644 --- a/docs/docs/reference/contextual/instance-defs.md +++ b/docs/docs/reference/contextual/instance-defs.md @@ -41,7 +41,7 @@ The name of an implied instance can be left out. So the implied instance definit of the last section can also be expressed like this: ```scala implied for Ord[Int] { ... } -implied [T: Ord] for Ord[List[T]] { ... } +implied [T] given (ord: Ord[T]) for Ord[List[T]] { ... } ``` If the name of an instance is missing, the compiler will synthesize a name from the type(s) in the `for` clause. From f397165a57173f0151ff8e39027f1b1650f60cbb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 4 Feb 2019 21:07:10 +0100 Subject: [PATCH 16/16] Address review comments --- docs/docs/reference/contextual/inferable-params.md | 2 +- docs/docs/reference/contextual/instance-defs.md | 12 +++++------- docs/docs/reference/contextual/query-types-spec.md | 10 +++++----- docs/docs/reference/contextual/query-types.md | 2 +- .../reference/contextual/relationship-implicits.md | 3 ++- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/docs/reference/contextual/inferable-params.md b/docs/docs/reference/contextual/inferable-params.md index 0eba6a74306b..a652e5a8f6f2 100644 --- a/docs/docs/reference/contextual/inferable-params.md +++ b/docs/docs/reference/contextual/inferable-params.md @@ -3,7 +3,7 @@ layout: doc-page title: "Inferable Parameters" --- -Functional programming tends to express most dependencies as simple functions parameterization. +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. Inferable parameters can help here since they enable the compiler to synthesize diff --git a/docs/docs/reference/contextual/instance-defs.md b/docs/docs/reference/contextual/instance-defs.md index 80ea0deadccd..d985f31a928f 100644 --- a/docs/docs/reference/contextual/instance-defs.md +++ b/docs/docs/reference/contextual/instance-defs.md @@ -30,9 +30,9 @@ implied ListOrd[T] given (ord: Ord[T]) for Ord[List[T]] { } ``` This code defines a trait `Ord` and two implied instance definitions. `IntOrd` defines -an implied instance for the type `Ord[Int]` whereas `ListOrd` defines implied -instances of `Ord[List[T]]` for all types `T` that come with an implied `Ord` instance themselves. -The `given` clause in `ListOrd` defines an [inferable parameter](./inferable-params.html). +an implied instance for the type `Ord[Int]` whereas `ListOrd[T]` defines implied +instances of `Ord[List[T]]` for all types `T` that come with an implied `Ord[T]` instance themselves. +The `given` clause in `ListOrd` defines an [inferable parameter](./inferable-params.html). Inferable parameters are further explained in the next section. ## Anonymous Implied Instances @@ -48,13 +48,11 @@ the type(s) in the `for` clause. ## Implied Alias Instances -An implied alias instance defines an implied instance that is equal to some expression. E.g., +An implied alias instance defines an implied instance that is equal to some expression. E.g., assuming a global method `currentThreadPool` returning a value with a member `context`, one could define: ```scala implied ctx for ExecutionContext = currentThreadPool().context ``` -Here, we create an implied instance `ctx` of type `ExecutionContext` that resolves to the -right hand side `currentThreadPool().context`. Each time an implied instance of `ExecutionContext` -is demanded, the result of evaluating the right-hand side expression is returned. +This creates an implied instance `ctx` of type `ExecutionContext` that resolves to the right hand side `currentThreadPool().context`. Each time an implied instance of `ExecutionContext` is demanded, the result of evaluating the right-hand side expression is returned. Alias instances may be anonymous, e.g. ```scala diff --git a/docs/docs/reference/contextual/query-types-spec.md b/docs/docs/reference/contextual/query-types-spec.md index d1ee471ae7df..67c627ce79f4 100644 --- a/docs/docs/reference/contextual/query-types-spec.md +++ b/docs/docs/reference/contextual/query-types-spec.md @@ -42,11 +42,11 @@ the inferable parameters `xi`. The query literal is evaluated as the instance creation expression: - - new scala.ImplicitFunctionN[T1, ..., Tn, T] { - def apply given (x1: T1, ..., xn: Tn): T = e - } - +```scala +new scala.ImplicitFunctionN[T1, ..., Tn, T] { + def apply given (x1: T1, ..., xn: Tn): T = e +} +``` In the case of a single untyped parameter, `given (x) => e` can be abbreviated to `given x => e`. diff --git a/docs/docs/reference/contextual/query-types.md b/docs/docs/reference/contextual/query-types.md index 43929f17c601..68adcb79b4d5 100644 --- a/docs/docs/reference/contextual/query-types.md +++ b/docs/docs/reference/contextual/query-types.md @@ -112,7 +112,7 @@ With that setup, the table construction code above compiles and expands to: ``` ### Example: Postconditions -As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, context query types, and extension methods to provide a zero-overhead abstraction. +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, context query types, and extension methods to provide a zero-overhead abstraction. ```scala object PostConditions { diff --git a/docs/docs/reference/contextual/relationship-implicits.md b/docs/docs/reference/contextual/relationship-implicits.md index 682ae9ddcd15..bfb4e3517070 100644 --- a/docs/docs/reference/contextual/relationship-implicits.md +++ b/docs/docs/reference/contextual/relationship-implicits.md @@ -82,7 +82,8 @@ The `infer` method corresponds to `implicitly` in Scala 2. 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. +with a normal application. Once old-style implicits are deprecated, context bounds +will map to inferable parameters instead. ### Extension Methods