From 20d0d59b8a59214ea33fb304f262b492803cc4e7 Mon Sep 17 00:00:00 2001 From: Bersier Date: Tue, 2 Jan 2024 05:46:47 -0500 Subject: [PATCH] Update context-functions.md Mainly fixed indentation --- .../reference/contextual/context-functions.md | 124 ++++++++++-------- 1 file changed, 67 insertions(+), 57 deletions(-) diff --git a/docs/_docs/reference/contextual/context-functions.md b/docs/_docs/reference/contextual/context-functions.md index 0ad3c8757782..0d174583f230 100644 --- a/docs/_docs/reference/contextual/context-functions.md +++ b/docs/_docs/reference/contextual/context-functions.md @@ -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` @@ -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 @@ -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 @@ -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: