Skip to content

Commit 232f438

Browse files
committed
Use with instead of given for context parameters
1 parent 9ab1842 commit 232f438

31 files changed

+641
-615
lines changed
Submodule scalap updated 1 file
Submodule stdLib213 updated 2361 files
Submodule utest updated 45 files

docs/docs/reference/contextual/implicit-by-name-parameters.md renamed to docs/docs/reference/contextual/by-name-context-parameters.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
---
22
layout: doc-page
3-
title: "Implicit By-Name Parameters"
3+
title: "By-Name Context Parameters"
44
---
55

6-
Implicit parameters can be declared by-name to avoid a divergent inferred expansion. Example:
6+
Context parameters can be declared by-name to avoid a divergent inferred expansion. Example:
77

88
```scala
99
trait Codec[T] {
1010
def write(x: T): Unit
1111
}
1212

13-
given intCodec: Codec[Int] = ???
13+
given intCodec as Codec[Int] = ???
1414

15-
given optionCodec[T]: (ev: => Codec[T]) => Codec[Option[T]] {
15+
given optionCodec[T] with (ev: => Codec[T]) as Codec[Option[T]] {
1616
def write(xo: Option[T]) = xo match {
1717
case Some(x) => ev.write(x)
1818
case None =>
@@ -24,29 +24,29 @@ val s = summon[Codec[Option[Int]]]
2424
s.write(Some(33))
2525
s.write(None)
2626
```
27-
As is the case for a normal by-name parameter, the argument for the implicit parameter `ev`
27+
As is the case for a normal by-name parameter, the argument for the context parameter `ev`
2828
is evaluated on demand. In the example above, if the option value `x` is `None`, it is
2929
not evaluated at all.
3030

31-
The synthesized argument for an implicit parameter is backed by a local val
31+
The synthesized argument for a context parameter is backed by a local val
3232
if this is necessary to prevent an otherwise diverging expansion.
3333

34-
The precise steps for synthesizing an argument for an implicit by-name parameter of type `=> T` are as follows.
34+
The precise steps for synthesizing an argument for a by-name context parameter of type `=> T` are as follows.
3535

36-
1. Create a new given instance of type `T`:
36+
1. Create a new given of type `T`:
3737

3838
```scala
39-
given lv: T = ???
39+
given lv as T = ???
4040
```
4141
where `lv` is an arbitrary fresh name.
4242

43-
1. This given 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 argument to an implicit by-name parameter.
43+
1. This given 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 argument to a by-name context parameter.
4444

4545
1. If this search succeeds with expression `E`, and `E` contains references to `lv`, replace `E` by
4646

4747

4848
```scala
49-
{ given lv: T = E; lv }
49+
{ given lv as T = E; lv }
5050
```
5151

5252
Otherwise, return `E` unchanged.
@@ -55,7 +55,7 @@ In the example above, the definition of `s` would be expanded as follows.
5555

5656
```scala
5757
val s = summon[Test.Codec[Option[Int]]](
58-
optionCodec[Int](intCodec)
58+
optionCodec[Int].with(intCodec)
5959
)
6060
```
6161

docs/docs/reference/contextual/context-bounds.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ title: "Context Bounds"
55

66
## Context Bounds
77

8-
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:
8+
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:
99
```scala
1010
def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max)
1111
```
12-
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.,
12+
A bound like `: Ord` on a type parameter `T` of a method or class indicates a context parameter `with Ord[T]`. The context parameter(s) generated from context bounds come last in the definition of the containing method or class. E.g.,
1313
```scala
14-
def f[T: C1 : C2, U: C3](x: T)(given y: U, z: V): R
14+
def f[T: C1 : C2, U: C3](x: T) with (y: U, z: V) : R
1515
```
1616
would expand to
1717
```scala
18-
def f[T, U](x: T)(given y: U, z: V)(given C1[T], C2[T], C3[U]): R
18+
def f[T, U](x: T) with (y: U, z: V) with C1[T], C2[T], C3[U]) : R
1919
```
2020
Context bounds can be combined with subtype bounds. If both are present, subtype bounds come first, e.g.
2121
```scala
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
layout: doc-page
3+
title: "Context Functions - More Details"
4+
---
5+
6+
## Syntax
7+
8+
Type ::= ...
9+
| FunArgTypes ‘?’ ‘=>’ Type
10+
Expr ::= ...
11+
| FunParams ‘?’ ‘=>’ Expr
12+
13+
Context function types associate to the right, e.g.
14+
`S? => T? => U` is the same as `S? => (T? => U)`.
15+
16+
## Implementation
17+
18+
Context function types are shorthands for class types that define `apply`
19+
methods with context parameters. Specifically, the `N`-ary function type
20+
`T1, ..., TN => R` is a shorthand for the class type
21+
`ContextFunctionN[T1 , ... , TN, R]`. Such class types are assumed to have the following definitions, for any value of `N >= 1`:
22+
```scala
23+
package scala
24+
trait ContextFunctionN[-T1 , ... , -TN, +R] {
25+
def apply with (x1: T1 , ... , xN: TN) : R
26+
}
27+
```
28+
Context function types erase to normal function types, so these classes are
29+
generated on the fly for typechecking, but not realized in actual code.
30+
31+
Context function literals `(x1: T1, ..., xn: Tn)? => e` map
32+
context parameters `xi` of types `Ti` to the result of evaluating the expression `e`.
33+
The scope of each context parameter `xi` is `e`. The parameters must have pairwise distinct names.
34+
35+
If the expected type of the context function literal is of the form
36+
`scala.ContextFunctionN[S1, ..., Sn, R]`, the expected type of `e` is `R` and
37+
the type `Ti` of any of the parameters `xi` can be omitted, in which case `Ti
38+
= Si` is assumed. If the expected type of the context function literal is
39+
some other type, all context parameter types must be explicitly given, and the expected type of `e` is undefined.
40+
The type of the context function literal is `scala.ContextFunctionN[S1, ...,Sn, T]`, where `T` is the widened
41+
type of `e`. `T` must be equivalent to a type which does not refer to any of
42+
the context parameters `xi`.
43+
44+
The context function literal is evaluated as the instance creation
45+
expression
46+
```scala
47+
new scala.ContextFunctionN[T1, ..., Tn, T] {
48+
def apply with (x1: T1, ..., xn: Tn) : T = e
49+
}
50+
```
51+
A context parameter may also be a wildcard represented by an underscore `_`. In that case, a fresh name for the parameter is chosen arbitrarily.
52+
53+
Note: The closing paragraph of the
54+
[Anonymous Functions section](https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#anonymous-functions)
55+
of Scala 2.12 is subsumed by context function types and should be removed.
56+
57+
Context function literals `(x1: T1, ..., xn: Tn)? => e` are
58+
automatically created for any expression `e` whose expected type is
59+
`scala.ContextFunctionN[T1, ..., Tn, R]`, unless `e` is
60+
itself a context function literal. This is analogous to the automatic
61+
insertion of `scala.Function0` around expressions in by-name argument position.
62+
63+
Context function types generalize to `N > 22` in the same way that function types do, see [the corresponding
64+
documentation](../dropped-features/limit22.md).
65+
66+
## Examples
67+
68+
See the section on Expressiveness from [Simplicitly: foundations and
69+
applications of implicit function
70+
types](https://dl.acm.org/citation.cfm?id=3158130).
71+
72+
### Type Checking
73+
74+
After desugaring no additional typing rules are required for context function types.
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
layout: doc-page
3+
title: "Context Functions"
4+
---
5+
6+
_Context functions_ are functions with (only) context parameters.
7+
Their types are _context function types_. Here is an example of a context function type:
8+
9+
```scala
10+
type Executable[T] = ExecutionContext? => T
11+
```
12+
A context function is applied to synthesized arguments, in
13+
the same way a method with context parameters is applied. For instance:
14+
```scala
15+
given ec as ExecutionContext = ...
16+
17+
def f(x: Int): Executable[Int] = ...
18+
19+
f(2).with(ec) // explicit argument
20+
f(2) // argument is inferred
21+
```
22+
Conversely, if the expected type of an expression `E` is a context function type
23+
`(T_1, ..., T_n)? => U` and `E` is not already an
24+
context function literal, `E` is converted to an context function literal by rewriting to
25+
```scala
26+
(x_1: T1, ..., x_n: Tn)? => E
27+
```
28+
where the names `x_1`, ..., `x_n` are arbitrary. This expansion is performed
29+
before the expression `E` is typechecked, which means that `x_1`, ..., `x_n`
30+
are available as givens in `E`.
31+
32+
Like their types, context function literals are written with a `?` after the parameters. They differ from normal function literals in that their types are context function types.
33+
34+
For example, continuing with the previous definitions,
35+
```scala
36+
def g(arg: Executable[Int]) = ...
37+
38+
g(22) // is expanded to g((ev: ExecutionContext)? => 22)
39+
40+
g(f(2)) // is expanded to g((ev: ExecutionContext)? => f(2).with(ev))
41+
42+
g((ctx: ExecutionContext)? => f(22).with(ctx)) // is left as it is
43+
```
44+
### Example: Builder Pattern
45+
46+
Context function types have considerable expressive power. For
47+
instance, here is how they can support the "builder pattern", where
48+
the aim is to construct tables like this:
49+
```scala
50+
table {
51+
row {
52+
cell("top left")
53+
cell("top right")
54+
}
55+
row {
56+
cell("bottom left")
57+
cell("bottom right")
58+
}
59+
}
60+
```
61+
The idea is to define classes for `Table` and `Row` that allow
62+
addition of elements via `add`:
63+
```scala
64+
class Table {
65+
val rows = new ArrayBuffer[Row]
66+
def add(r: Row): Unit = rows += r
67+
override def toString = rows.mkString("Table(", ", ", ")")
68+
}
69+
70+
class Row {
71+
val cells = new ArrayBuffer[Cell]
72+
def add(c: Cell): Unit = cells += c
73+
override def toString = cells.mkString("Row(", ", ", ")")
74+
}
75+
76+
case class Cell(elem: String)
77+
```
78+
Then, the `table`, `row` and `cell` constructor methods can be defined
79+
with context function types as parameters to avoid the plumbing boilerplate
80+
that would otherwise be necessary.
81+
```scala
82+
def table(init: Table? => Unit) = {
83+
given t as Table
84+
init
85+
t
86+
}
87+
88+
def row(init: Row? => Unit) with (t: Table) = {
89+
given r as Row
90+
init
91+
t.add(r)
92+
}
93+
94+
def cell(str: String) with (r: Row) =
95+
r.add(new Cell(str))
96+
```
97+
With that setup, the table construction code above compiles and expands to:
98+
```scala
99+
table { ($t: Table)? =>
100+
101+
row { ($r: Row)? =>
102+
cell("top left").with($r)
103+
cell("top right").with($r)
104+
}.with($t)
105+
106+
row { ($r: Row)? =>
107+
cell("bottom left").with($r)
108+
cell("bottom right").with($r)
109+
}.with($t)
110+
}
111+
```
112+
### Example: Postconditions
113+
114+
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 function types, and extension methods to provide a zero-overhead abstraction.
115+
116+
```scala
117+
object PostConditions {
118+
opaque type WrappedResult[T] = T
119+
120+
def result[T] with (r: WrappedResult[T]) : T = r
121+
122+
def (x: T) ensuring[T](condition: WrappedResult[T]? => Boolean): T = {
123+
assert(condition.with(x))
124+
x
125+
}
126+
}
127+
import PostConditions.{ensuring, result}
128+
129+
val s = List(1, 2, 3).sum.ensuring(result == 6)
130+
```
131+
**Explanations**: We use a context function type `WrappedResult[T]? => Boolean`
132+
as the type of the condition of `ensuring`. An argument to `ensuring` such as
133+
`(result == 6)` will therefore have a given of type `WrappedResult[T]` in
134+
scope to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure
135+
that we do not get unwanted givens in scope (this is good practice in all cases
136+
where context parameters are involved). Since `WrappedResult` is an opaque type alias, its
137+
values need not be boxed, and since `ensuring` is added as an extension method, its argument
138+
does not need boxing either. Hence, the implementation of `ensuring` is as about as efficient
139+
as the best possible code one could write by hand:
140+
141+
```scala
142+
{ val result = List(1, 2, 3).sum
143+
assert(result == 6)
144+
result
145+
}
146+
```
147+
### Reference
148+
149+
For more info, see the [blog article](https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html),
150+
(which uses a different syntax that has been superseded).
151+
152+
[More details](./implicit-function-types-spec.md)

0 commit comments

Comments
 (0)