Skip to content

Commit

Permalink
Update docs to new conventions
Browse files Browse the repository at this point in the history
Also: Some reshuffling and editing of existing material.
  • Loading branch information
odersky committed Sep 30, 2024
1 parent ca6b8f7 commit d8c33ad
Show file tree
Hide file tree
Showing 22 changed files with 764 additions and 655 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ trait Codec[T]:

given intCodec: Codec[Int] = ???

given optionCodec[T](using ev: => Codec[T]): Codec[Option[T]] with
given optionCodec[T](using ev: => Codec[T]): Codec[Option[T]]:
def write(xo: Option[T]) = xo match
case Some(x) => ev.write(x)
case None =>
Expand Down
193 changes: 174 additions & 19 deletions docs/_docs/reference/contextual/context-bounds.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,205 @@ title: "Context Bounds"
nightlyOf: https://docs.scala-lang.org/scala3/reference/contextual/context-bounds.html
---

A context bound is a shorthand for expressing the common pattern of a context parameter that depends on a type parameter. Using a context bound, the `maximum` function of the last section can be written like this:
A context bound is a shorthand for expressing the common pattern of a context parameter that depends on a type parameter. These patterns are commonplace when modelling type classes in Scala. Using a context bound, the `maximum` function of the [last section](./using-clauses.md) can be written like this:

```scala
def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max)
```

A bound like `: Ord` on a type parameter `T` of a method or class indicates a context parameter `using Ord[T]`. The context parameter(s) generated from context bounds
are added as follows:
A bound like `: Ord` on a type parameter `T` of a method or class indicates a context parameter `using Ord[T]`, which is added to the signature of the enclosing method. The generated parameter is called a _witness_ for the context bound.

- If the method parameters end in an implicit parameter list or using clause,
context parameters are added in front of that list.
- Otherwise they are added as a separate parameter clause at the end.

Example:
For instance the `maximum` method above expands to
```scala
def maximum[T](xs: List[T])(using Ord[T]): T = ...
```
Context bounds can be combined with subtype bounds. If both are present, subtype bounds come first, e.g.

```scala
def f[T: C1 : C2, U: C3](x: T)(using y: U, z: V): R
def f[T <: B : C](x: T): R = ...
```

would expand to
## Named Context Bounds

A context bound can be given a name with an `as` clause. For example, assume the following trait definitions.
```scala
trait SemiGroup[A]:
extension (x: A) def combine(y: A): A

trait Monoid[A] extends SemiGroup[A]:
def unit: A
```
We can write `reduce` function over lists of monoid instances like this:
```scala
def f[T, U](x: T)(using _: C1[T], _: C2[T], _: C3[U], y: U, z: V): R
def reduce[A: Monoid as m](xs: List[A]): A =
xs.foldLeft(m.unit)(_ `combine` _)
```
We use `as x` after the type of a context bound to bind the instance to `x`. This is analogous to import renaming, which also introduces a new name for something that comes before.

Context bounds can be combined with subtype bounds. If both are present, subtype bounds come first, e.g.
In a context bound with a naming clause the witness parameter carries the given name. For instance the expanded signature of `reduce` would be
```scala
def reduce[A](xs: List[A])(using m: Monoid[A]): A
```
Since the context parameter now has a name, it can be referred
to in the body of `reduce`. An example is the `m.unit` reference in the definition above.

If the context bound does not carry an `as` clause, the generated witness parameter gets a compiler-synthesized name. However, a [currently experimental
language extension](../experimental/default-names-context-bounds.md) would in this case give the context parameter the same name as the bound type parameter.

Named context bounds were introduced in Scala 3.6.

## Aggregate Context Bounds

A type parameter can have several context bounds. If there are multiple bounds, they are written inside braces `{...}`. Example:
```scala
def g[T <: B : C](x: T): R = ...
trait:
def showMax[X : {Ord, Show}](x: X, y: X): String
class B extends A:
def showMax[X : {Ord as ordering, Show as show}](x: X, y: X): String =
show.asString(ordering.max(x, y))
```

## Migration
This syntax is valid from Scala 3.6. The previous syntax used
chains of `:` clauses, as in `[X : Ord : Show]`. This syntax is still available but will be deprecated and removed over time.

To ease migration, context bounds in Dotty map in Scala 3.0 to old-style implicit parameters
for which arguments can be passed either with a `(using ...)` clause or with a normal application. From Scala 3.1 on, they will map to context parameters instead, as is described above.
## Placement of Generated Context Parameters

If the source version is `future-migration`, any pairing of an evidence
The witness context parameter(s) generated from context bounds are added as follows:

1. If one of the bounds is referred to by its name in a subsequent parameter clause, the context bounds are mapped to a using clause immediately preceding the first such parameter clause.
2. Otherwise, if the last parameter clause is a using (or implicit) clause, merge all parameters arising from context bounds in front of that clause, creating a single using clause.
3. Otherwise, let the parameters arising from context bounds form a new using clause at the end.

Rules (2) and (3) match Scala 2's rules. Rule (1) is new but since context bounds so far could not be referred to, it does not apply to legacy code. Therefore, binary compatibility with Scala 2 and earlier Scala 3 versions is maintained.

**Examples:**

1. By rule 3,
```scala
def f[T: {C1, C2}](x: T): R
```
expands to
```scala
def f[T](x: T)(using C1, C2): R
```
Equally by rule 3,
```scala
def f[T: {C1 as c1, C2 as c2}](x: T): R
```
expands to
```scala
def f[T](x: T)(using c1: C1, c2: C2): R

2. By rule 2,
```scala
def f[T: C1 : C2, U: C3](x: T)(using y: U, z: V): R
```
expands to
```scala
def f[T, U](x: T)(using _: C1[T], _: C2[T], _: C3[U], y: U, z: V): R
```
The same expansion occurs if `y` and `z` are Scala 2 style `implicit` parameters.
3. Assume the following trait definition:
```scala
trait Parser[P]:
type Input
type Result
```
Here is a method `run` that runs a parser on an input of the required type:
```scala
def run[P : Parser as p](in: p.Input): p.Result
```
By rule 1, this method definition is expanded to:
```scala
def run[P](using p: Parser[P]](in: p.Input): p.Result
```
Note that the `using` clause is placed in front of the explicit parameter clause `(in: p.Result)` so that
the type `p.Result` can legally refer to the context parameter `p`.

### Migration

To ease migration, context bounds map in Scala 3.0 - 3.5 to old-style implicit parameters
for which arguments can be passed either with a `(using ...)` clause or with a normal application. From Scala 3.6 on, they will map to context parameters instead, as is described above.

If the source version is `3.6-migration`, any pairing of an evidence
context parameter stemming from a context bound with a normal argument will give a migration
warning. The warning indicates that a `(using ...)` clause is needed instead. The rewrite can be
done automatically under `-rewrite`.

## Context Bounds for Polymorphic Functions

From Scala 3.6 on, context bounds can also be used in polymorphic function types and polymorphic function literals:

```scala
type Comparer = [X: Ord] => (x: X, y: X) => Boolean
val less: Comparer = [X: Ord as ord] => (x: X, y: X) =>
ord.compare(x, y) < 0
```

The expansion of such context bounds is analogous to the expansion in method types, except that instead of adding a using clause in a method, we insert a [context function type](./context-functions.md).

For instance, the `type` and `val` definitions above would expand to
```scala
type Comparer = [X] => (x: X, y: X) => Ord[X] ?=> Boolean
val less: Comparer = [X] => (x: X, y: X) => (ord: Ord[X]) ?=>
ord.compare(x, y) < 0
```

The expansion of using clauses does look inside alias types. For instance,
here is a variation of the previous example that uses a parameterized type alias:
```scala
type Cmp[X] = (x: X, y: X) => Ord[X] ?=> Boolean
type Comparer2 = [X: Ord] => Cmp[X]
```
The expansion of the right hand side of `Comparer2` expands the `Cmp[X]` alias
and then inserts the context function at the same place as what's done for `Comparer`.

### Context Bounds for Type Members

From Scala 3.6 on, context bounds can not only used for type parameters but also for abstract type members.

**Example**:

```scala
class Collection:
type Element: Ord
```

These context bounds have to expand differently from context bounds for type parameters since there is no parameter list to accommodate any generated witnesses. Instead, context bounds for abstract types map to
[deferred givens](./deferred-givens.md).

For instance, the `Collection` class above expands to:
```scala
class Collection:
type Element
given Ord[Element] = deferred
```
As is explain in the [section on deferred givens](./deferred-givens.md), `deferred` is a special name defined in the `scala.compiletime` package.


## Syntax

The new syntax of context bounds is as follows:

```ebnf
TypeParamBounds ::= [SubtypeBounds] {ContextBound}
ContextBound ::= ‘:’ Type
TypeParamBounds ::= TypeAndCtxBounds
TypeAndCtxBounds ::= TypeBounds [‘:’ ContextBounds]
ContextBounds ::= ContextBound
| '{' ContextBound {',' ContextBound} '}'
ContextBound ::= Type ['as' id]
```

The syntax of function types and function literals
is generalized as follows to allow context bounds for generic type parameters.

```ebnf
FunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type
| DefTypeParamClause '=>' Type
FunExpr ::= FunParams (‘=>’ | ‘?=>’) Expr
| DefTypeParamClause ‘=>’ Expr
```
The syntax for abstract type members is generalized as follows to allow context bounds:

```scala
TypeDef ::= id [TypeParamClause] TypeAndCtxBounds
```
2 changes: 1 addition & 1 deletion docs/_docs/reference/contextual/conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ abstract class Conversion[-T, +U] extends (T => U):
```
For example, here is an implicit conversion from `String` to `Token`:
```scala
given Conversion[String, Token] with
given Conversion[String, Token]:
def apply(str: String): Token = new KeyWord(str)
```
Using an alias this can be expressed more concisely as:
Expand Down
57 changes: 57 additions & 0 deletions docs/_docs/reference/contextual/deferred-givens.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
layout: doc-page
title: "Deferred Givens"
nightlyOf: https://docs.scala-lang.org/scala3/reference/contextual/deferred-givens.html
---

Scala 3.6 introduces a new way to implement a given definition in a trait like this:
```scala
given T = deferred
```
Such givens can be implemented automatically in subclasses. `deferred` is a new method in the `scala.compiletime` package, which can appear only as the right hand side of a given defined in a trait. Any class implementing that trait will provide an implementation of this given. If a definition is not provided explicitly, it will be synthesized by searching for a given of type `T` in the scope of the inheriting class. Specifically, the scope in which this given will be searched is the environment of that class augmented by its parameters but not containing its members (since that would lead to recursive resolutions). If an implementation _is_ provided explicitly, it counts as an override of a concrete definition and needs an `override` modifier.

Deferred givens allow a clean implementation of context bounds in traits,
as in the following example:
```scala
trait Sorted:
type Element : Ord

class SortedSet[A : Ord as ord] extends Sorted:
type Element = A
```
The compiler expands this to the following implementation.
```scala
trait Sorted:
type Element
given Ord[Element] = compiletime.deferred

class SortedSet[A](using ord: Ord[A]) extends Sorted:
type Element = A
override given Ord[Element] = ord
```

The using clause in class `SortedSet` provides an implementation for the deferred given in trait `Sorted`.

One can also provide an explicit implementation of a deferred given, as in the following example:

```scala
class SortedString[A] extends Sorted:
type Element = String
override given Ord[String] = ...
```

Note that the implementing given needs an `override` modifier since the `deferred` given in class `Sorted` counts as a concrete (i.e. not abstract) definition. In a sense, the `deferred` right hand side in `Sorted` is like a (magic, compiler-supported) macro, with the peculiarity that the macro's implementation also affects subclasses.

## Abstract Givens

A given may also be an abstract member, with the restriction that it must have an explicit name. Example:

```scala
trait HasOrd[T]:
given ord: Ord[T]
```
An abstract given has the form `given name: Type` without a right-hand side or arguments to the type.

Since Scala 3.6, abstract givens are made redundant by deferred givens. Deferred givens can replace abstract givens. They have better ergonomics, since deferred givens get naturally implemented in inheriting classes, so there is no longer any need for boilerplate to fill in definitions of abstract givens.

It is therefore recommended that software architectures relying on abstract givens be migrated to use deferred givens instead. Abstract givens are still supported in Scala 3.6, but will likely be deprecated and phased out over time.
4 changes: 2 additions & 2 deletions docs/_docs/reference/contextual/derivation-macro.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ trait Eq[T]:
def eqv(x: T, y: T): Boolean

object Eq:
given Eq[String] with
given Eq[String]:
def eqv(x: String, y: String) = x == y

given Eq[Int] with
given Eq[Int]:
def eqv(x: Int, y: Int) = x == y

def eqProduct[T](body: (T, T) => Boolean): Eq[T] =
Expand Down
Loading

0 comments on commit d8c33ad

Please sign in to comment.