Skip to content

Commit

Permalink
Update context-functions.md (#19355)
Browse files Browse the repository at this point in the history
Mainly fixed indentation
  • Loading branch information
nicolasstucki authored Jan 3, 2024
2 parents b36f728 + 20d0d59 commit e9d38aa
Showing 1 changed file with 67 additions and 57 deletions.
124 changes: 67 additions & 57 deletions docs/_docs/reference/contextual/context-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,29 @@ _Context functions_ are functions with (only) context parameters.
Their types are _context function types_. Here is an example of a context function type:

```scala
import scala.concurrent.ExecutionContext

type Executable[T] = ExecutionContext ?=> T
```
Context functions are written using `?=>` as the "arrow" sign.
They are applied to synthesized arguments, in
the same way methods with context parameters are applied. For instance:
```scala
given ec: ExecutionContext = ...
given ec: ExecutionContext = ...

def f(x: Int): ExecutionContext ?=> Int = ...
def f(x: Int): ExecutionContext ?=> Int = ...

// could be written as follows with the type alias from above
// def f(x: Int): Executable[Int] = ...
// could be written as follows with the type alias from above
// def f(x: Int): Executable[Int] = ...

f(2)(using ec) // explicit argument
f(2) // argument is inferred
f(2)(using ec) // explicit argument
f(2) // argument is inferred
```
Conversely, if the expected type of an expression `E` is a context function type
`(T_1, ..., T_n) ?=> U` and `E` is not already an
context function literal, `E` is converted to a context function literal by rewriting it to
```scala
(x_1: T1, ..., x_n: Tn) ?=> E
(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`
Expand All @@ -38,14 +40,14 @@ Like their types, context function literals are written using `?=>` as the arrow

For example, continuing with the previous definitions,
```scala
def g(arg: Executable[Int]) = ...
def g(arg: Executable[Int]) = ...

g(22) // is expanded to g((ev: ExecutionContext) ?=> 22)
g(22) // is expanded to g((ev: ExecutionContext) ?=> 22)

g(f(2)) // is expanded to g((ev: ExecutionContext) ?=> f(2)(using ev))
g(f(2)) // is expanded to g((ev: ExecutionContext) ?=> f(2)(using ev))

g((ctx: ExecutionContext) ?=> f(3)) // is expanded to g((ctx: ExecutionContext) ?=> f(3)(using ctx))
g((ctx: ExecutionContext) ?=> f(3)(using ctx)) // is left as it is
g((ctx: ExecutionContext) ?=> f(3)) // is expanded to g((ctx: ExecutionContext) ?=> f(3)(using ctx))
g((ctx: ExecutionContext) ?=> f(3)(using ctx)) // is left as it is
```

## Example: Builder Pattern
Expand All @@ -54,63 +56,65 @@ Context 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:
```scala
table {
row {
cell("top left")
cell("top right")
}
row {
cell("bottom left")
cell("bottom right")
}
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 the
addition of elements via `add`:
```scala
class Table:
val rows = new ArrayBuffer[Row]
def add(r: Row): Unit = rows += r
override def toString = rows.mkString("Table(", ", ", ")")
import scala.collection.mutable.ArrayBuffer

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(", ", ", ")")
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)
case class Cell(elem: String)
```
Then, the `table`, `row` and `cell` constructor methods can be defined
with context function types as parameters to avoid the plumbing boilerplate
that would otherwise be necessary.
```scala
def table(init: Table ?=> Unit) =
given t: Table = Table()
init
t

def row(init: Row ?=> Unit)(using t: Table) =
given r: Row = Row()
init
t.add(r)

def cell(str: String)(using r: Row) =
r.add(new Cell(str))
def table(init: Table ?=> Unit) =
given t: Table = Table()
init
t

def row(init: Row ?=> Unit)(using t: Table) =
given r: Row = Row()
init
t.add(r)

def cell(str: String)(using r: Row) =
r.add(new Cell(str))
```
With that setup, the table construction code above compiles and expands to:
```scala
table { ($t: Table) ?=>

row { ($r: Row) ?=>
cell("top left")(using $r)
cell("top right")(using $r)
}(using $t)

row { ($r: Row) ?=>
cell("bottom left")(using $r)
cell("bottom right")(using $r)
}(using $t)
}
table { ($t: Table) ?=>

row { ($r: Row) ?=>
cell("top left")(using $r)
cell("top right")(using $r)
}(using $t)

row { ($r: Row) ?=>
cell("bottom left")(using $r)
cell("bottom right")(using $r)
}(using $t)
}
```
## Example: Postconditions

Expand All @@ -131,12 +135,18 @@ import PostConditions.{ensuring, result}

val s = List(1, 2, 3).sum.ensuring(result == 6)
```
**Explanations**: We use a context function type `WrappedResult[T] ?=> Boolean`
### Explanation

We use a context function type `WrappedResult[T] ?=> Boolean`
as the type of the condition of `ensuring`. An argument to `ensuring` such as
`(result == 6)` will therefore have a given of type `WrappedResult[T]` in
scope to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure
scope to pass along to the `result` method.

`WrappedResult` is a fresh type, to make sure
that we do not get unwanted givens in scope (this is good practice in all cases
where context parameters are involved). Since `WrappedResult` is an opaque type alias, its
where context 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 close in efficiency to the best possible code one could write by hand:

Expand Down

0 comments on commit e9d38aa

Please sign in to comment.