Skip to content

Trial: Use "default" for given instances #7941

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ FunType ::= FunArgTypes ‘=>’ Type
FunArgTypes ::= InfixType
| ‘(’ [ ‘[given]’ FunArgType {‘,’ FunArgType } ] ‘)’
| ‘(’ ‘[given]’ TypedFunParam {‘,’ TypedFunParam } ‘)’
GivenArgs ::= InfixType
| ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’
| ‘(’ ‘val’ TypedFunParam {‘,’ ‘val’ TypedFunParam } ‘)’
TypedFunParam ::= id ‘:’ Type
MatchType ::= InfixType `match` ‘{’ TypeCaseClauses ‘}’
InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2)
Expand Down
30 changes: 30 additions & 0 deletions docs/docs/reference/contextual-defaults/context-bounds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
layout: doc-page
title: "Context Bounds"
---

## Context Bounds

A context bound is a shorthand for expressing the common pattern of an implicit parameter that depends on a type parameter. Using a context bound, the `maximum` function of the last section can be written like this:
```scala
def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max)
```
A bound like `: Ord` on a type parameter `T` of a method or class indicates an implicit parameter `(given Ord[T])`. The implicit parameter(s) generated from context bounds come last in the definition of the containing method or class. E.g.,
```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 = ...
```

## Syntax

```
TypeParamBounds ::= [SubtypeBounds] {ContextBound}
ContextBound ::= ‘:’ Type
```
75 changes: 75 additions & 0 deletions docs/docs/reference/contextual-defaults/conversions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
layout: doc-page
title: "Implicit Conversions"
---

Implicit conversions are defined by default 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
default for Conversion[String, Token] {
def apply(str: String): Token = new KeyWord(str)
}
```
Using an alias this can be expressed more concisely as:
```scala
default for Conversion[String, Token] = new KeyWord(_)
```
An implicit conversion is applied automatically by the compiler in three situations:

1. If an expression `e` has type `T`, and `T` does not conform to the expression's expected type `S`.
2. In a selection `e.m` with `e` of type `T`, but `T` defines no member `m`.
3. In an application `e.m(args)` with `e` of type `T`, if `T` does define
some member(s) named `m`, but none of these members can be applied to the arguments `args`.

In the first case, the compiler looks for a `scala.Conversion` default that maps
an argument of type `T` to type `S`. In the second and third
case, it looks for a `scala.Conversion` default 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 a default `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
default int2Integer for Conversion[Int, java.lang.Integer] =
java.lang.Integer.valueOf(_)
```

2. The "magnet" pattern is sometimes used to express many variants of a method. Instead of defining overloaded versions of the method, one can also let the method take one or more arguments of specially defined "magnet" types, into which various argument types can be converted. E.g.
```scala
object Completions {

// The argument "magnet" type
enum CompletionArg {
case Error(s: String)
case Response(f: Future[HttpResponse])
case Status(code: Future[StatusCode])
}
object CompletionArg {

// conversions defining the possible arguments to pass to `complete`
// these always come with CompletionArg
// They can be invoked explicitly, e.g.
//
// CompletionArg.fromStatusCode(statusCode)

default fromString for Conversion[String, CompletionArg] = Error(_)
default fromFuture for Conversion[Future[HttpResponse], CompletionArg] = Response(_)
default fromStatusCode for Conversion[Future[StatusCode], CompletionArg] = Status(_)
}
import CompletionArg._

def complete[T](arg: CompletionArg) = arg match {
case Error(s) => ...
case Response(f) => ...
case Status(code) => ...
}
}
```
This setup is more complicated than simple overloading of `complete`, but it can still be useful if normal overloading is not available (as in the case above, since we cannot have two overloaded methods that take `Future[...]` arguments), or if normal overloading would lead to a combinatorial explosion of variants.
118 changes: 118 additions & 0 deletions docs/docs/reference/contextual-defaults/default-imports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
layout: doc-page
title: "Default Imports"
---

A special form of import wildcard selector is used to import default. Example:
```scala
object A {
class TC
default tc of TC
def f(given TC) = ???
}
object B {
import A._
import A.{default _}
}
```
In the code above, the `import A._` clause of object `B` will import all members
of `A` _except_ the default `tc`. Conversely, the second import `import A.{default _}`
will import _only_ that default. The two import clauses can also be merged into one:
```scala
object B
import A.{default _, _}
```

Generally, a normal wildcard selector `_` brings all definitions other than defaults or extensions into scope
whereas a `default _` selector brings all defaults (including those resulting from extensions) into scope.

There are two main benefits arising from these rules:

- It is made clearer where defaults in scope are coming from.
In particular, it is not possible to hide imported defaults in a long list of regular wildcard imports.
- It enables importing all defaults
without importing anything else. This is particularly important since defaults
can be anonymous, so the usual recourse of using named imports is not
practical.

### Importing By Type

Since defaults can be anonymous it is not always practical to import them by their name, and wildcard imports are typically used instead. By-type imports provide a more specific alternative to wildcard imports, which makes it clearer what is imported. Example:

```scala
import A.{default TC}
```
This imports any default in `A` that has a type which conforms to `TC`. Importing defaults of several types `T1,...,Tn`
is expressed by multiple `default` selectors.
```
import A.{default T1, ..., default Tn}
```
Importing all defaults of a parameterized type is expressed by wildcard arguments.
For instance, assuming the object
```scala
object Instances {
default intOrd for Ordering[Int]
default [T: Ordering] listOrd for Ordering[List[T]]
default ec for ExecutionContext = ...
default im for Monoid[Int]
}
```
the import
```scala
import Instances.{default Ordering[?], default ExecutionContext}
```
would import the `intOrd`, `listOrd`, and `ec` instances but leave out the `im` instance, since it fits none of the specified bounds.

By-type imports can be mixed with by-name imports. If both are present in an import clause, by-type imports come last. For instance, the import clause
```scala
import Instances.{im, default Ordering[?]}
```
would import `im`, `intOrd`, and `listOrd` but leave out `ec`.

<!--
Bounded wildcard selectors also work for normal imports and exports. For instance, consider the following `enum` definition:
```scala
enum Color {
case Red, Green, Blue, Magenta

def isPrimary(c: Color): Boolean = ...
}
export Color.{_: Color}
```
The export clause makes all four `Color` values available as unqualified constants, but
leaves the `isPrimary` method alone.
-->

### Migration

The rules for imports stated above have the consequence that a library
would have to migrate in lockstep with all its users from old style implicits and
normal imports to defaults and default imports.

The following modifications avoid this hurdle to migration.

1. A `default` import selector also brings old style implicits into scope. So, in Scala 3.0
an old-style implicit definition can be brought into scope either by a `_` or a `default _` wildcard selector.

2. In Scala 3.1, old-style implicits accessed through a `_` wildcard import will give a deprecation warning.

3. In some version after 3.1, old-style implicits accessed through a `_` wildcard import will give a compiler error.

These rules mean that library users can use `default _` selectors to access old-style implicits in Scala 3.0,
and will be gently nudged and then forced to do so in later versions. Libraries can then switch to
representation clauses once their user base has migrated.

### Syntax

```
Import ::= ‘import’ ImportExpr {‘,’ ImportExpr}
ImportExpr ::= StableId ‘.’ ImportSpec
ImportSpec ::= id
| ‘_’
| ‘{’ ImportSelectors) ‘}’
ImportSelectors ::= id [‘=>’ id | ‘=>’ ‘_’] [‘,’ ImportSelectors]
| WildCardSelector {‘,’ WildCardSelector}
WildCardSelector ::= ‘_'
| ‘default’ (‘_' | InfixType)
Export ::= ‘export’ ImportExpr {‘,’ ImportExpr}
```
118 changes: 118 additions & 0 deletions docs/docs/reference/contextual-defaults/default-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
layout: doc-page
title: "Implicit Parameters"
---

Functional programming tends to express most dependencies as simple function parameterization.
This is clean and powerful, but it sometimes leads to functions that take many parameters and
call trees where the same value is passed over and over again in long call chains to many
functions. Implicit parameters can help here since they enable the compiler to synthesize
repetitive arguments instead of the programmer having to write them explicitly.

For example, with the [default instances](./defaults.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)(ord: Ord[T]): T =
if (ord.compare(x, y) < 0) y else x
```
Here, `ord` is an _implicit parameter_ introduced with a `given` clause.
The `max` method can be applied as follows:
```scala
max(2, 3)(default intOrd)
```
The `(given intOrd)` part passes `intOrd` as an argument for the `ord` parameter. But the point of
implicit parameters is that this argument can also be left out (and it usually is). So the following
applications are equally valid:
```scala
max(2, 3)
max(List(1, 2, 3), Nil)
```

def foo(Int?, y: Int = 2)
foo(_)

## Anonymous Given Clauses

In many situations, the name of an implicit parameter need not be
mentioned explicitly at all, since it is used only in synthesized arguments for
other implicit parameters. In that case one can avoid defining a parameter name
and just provide its type. Example:
```scala
def maximum[T](xs: List[T])(given Ord[T]): T =
xs.reduceLeft(max)
```
`maximum` takes an implicit parameter of type `Ord` only to pass it on as an
inferred argument to `max`. The name of the parameter is left out.

Generally, implicit parameters may be defined either as a full parameter list `(given p_1: T_1, ..., p_n: T_n)` or just as a sequence of types `(given T_1, ..., T_n)`. Vararg implicit parameters are not supported.

## Inferring Complex Arguments

Here are two other methods that have an implicit parameter of type `Ord[T]`:
```scala
def descending[T](given asc: Ord[T]): Ord[T] = new Ord[T] {
def compare(x: T, y: T) = asc.compare(y, x)
}

def minimum[T](xs: List[T])(given Ord[T]) =
maximum(xs)(given descending)
```
The `minimum` method's right hand side passes `descending` as an explicit argument to `maximum(xs)`.
With this setup, the following calls are all well-formed, and they all normalize to the last one:
```scala
minimum(xs)
maximum(xs)(given descending)
maximum(xs)(given descending(given listOrd))
maximum(xs)(given descending(given listOrd(given intOrd)))
```

## Multiple Given Clauses

There can be several implicit parameter clauses in a definition and implicit parameter clauses can be freely
mixed with normal ones. Example:
```scala
def f(u: Universe)(given ctx: u.Context)(given s: ctx.Symbol, k: ctx.Kind) = ...
```
Multiple given clauses are matched left-to-right in applications. Example:
```scala
object global extends Universe { type Context = ... }
default ctx for global.Context { type Symbol = ...; type Kind = ... }
default sym for ctx.Symbol
default kind for ctx.Kind
```
Then the following calls are all valid (and normalize to the last one)
```scala
f
f(global)
f(global)(given ctx)
f(global)(given ctx)(given sym, kind)
```
But `f(global)(given sym, kind)` would give a type error.

## Summoning Instances

The method `summon` in `Predef` returns the default of a specific type. For example,
the default for `Ord[List[Int]]` is produced by
```scala
summon[Ord[List[Int]]] // reduces to listOrd(given intOrd)
```
The `summon` method is simply defined as the (non-widening) identity function over an implicit parameter.
```scala
def summon[T](given x: T): x.type = x
```

## Syntax

Here is the new syntax of parameters and arguments seen as a delta from the [standard context free syntax of Scala 3](../../internals/syntax.md).
```
ClsParamClauses ::= ...
| {ClsParamClause} {GivenClsParamClause}
GivenClsParamClause ::= ‘(’ ‘given’ (ClsParams | GivenTypes) ‘)’
DefParamClauses ::= ...
| {DefParamClause} {GivenParamClause}
GivenParamClause ::= ‘(’ ‘given’ (DefParams | GivenTypes) ‘)’
GivenTypes ::= AnnotType {‘,’ AnnotType}

ParArgumentExprs ::= ...
| ‘(’ ‘given’ ExprsInParens ‘)’
```
Loading