@@ -8,27 +8,29 @@ _Context functions_ are functions with (only) context parameters.
8
8
Their types are _ context function types_ . Here is an example of a context function type:
9
9
10
10
``` scala
11
+ import scala .concurrent .ExecutionContext
12
+
11
13
type Executable [T ] = ExecutionContext ?=> T
12
14
```
13
15
Context functions are written using ` ?=> ` as the "arrow" sign.
14
16
They are applied to synthesized arguments, in
15
17
the same way methods with context parameters are applied. For instance:
16
18
``` scala
17
- given ec : ExecutionContext = ...
19
+ given ec : ExecutionContext = ...
18
20
19
- def f (x : Int ): ExecutionContext ?=> Int = ...
21
+ def f (x : Int ): ExecutionContext ?=> Int = ...
20
22
21
- // could be written as follows with the type alias from above
22
- // def f(x: Int): Executable[Int] = ...
23
+ // could be written as follows with the type alias from above
24
+ // def f(x: Int): Executable[Int] = ...
23
25
24
- f(2 )(using ec) // explicit argument
25
- f(2 ) // argument is inferred
26
+ f(2 )(using ec) // explicit argument
27
+ f(2 ) // argument is inferred
26
28
```
27
29
Conversely, if the expected type of an expression ` E ` is a context function type
28
30
` (T_1, ..., T_n) ?=> U ` and ` E ` is not already an
29
31
context function literal, ` E ` is converted to a context function literal by rewriting it to
30
32
``` scala
31
- (x_1 : T1 , ..., x_n : Tn ) ?=> E
33
+ (x_1 : T1 , ..., x_n : Tn ) ?=> E
32
34
```
33
35
where the names ` x_1 ` , ..., ` x_n ` are arbitrary. This expansion is performed
34
36
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
38
40
39
41
For example, continuing with the previous definitions,
40
42
``` scala
41
- def g (arg : Executable [Int ]) = ...
43
+ def g (arg : Executable [Int ]) = ...
42
44
43
- g(22 ) // is expanded to g((ev: ExecutionContext) ?=> 22)
45
+ g(22 ) // is expanded to g((ev: ExecutionContext) ?=> 22)
44
46
45
- g(f(2 )) // is expanded to g((ev: ExecutionContext) ?=> f(2)(using ev))
47
+ g(f(2 )) // is expanded to g((ev: ExecutionContext) ?=> f(2)(using ev))
46
48
47
- g((ctx : ExecutionContext ) ?=> f(3 )) // is expanded to g((ctx: ExecutionContext) ?=> f(3)(using ctx))
48
- g((ctx : ExecutionContext ) ?=> f(3 )(using ctx)) // is left as it is
49
+ g((ctx : ExecutionContext ) ?=> f(3 )) // is expanded to g((ctx: ExecutionContext) ?=> f(3)(using ctx))
50
+ g((ctx : ExecutionContext ) ?=> f(3 )(using ctx)) // is left as it is
49
51
```
50
52
51
53
## Example: Builder Pattern
@@ -54,63 +56,65 @@ Context function types have considerable expressive power. For
54
56
instance, here is how they can support the "builder pattern", where
55
57
the aim is to construct tables like this:
56
58
``` scala
57
- table {
58
- row {
59
- cell(" top left" )
60
- cell(" top right" )
61
- }
62
- row {
63
- cell(" bottom left" )
64
- cell(" bottom right" )
65
- }
59
+ table {
60
+ row {
61
+ cell(" top left" )
62
+ cell(" top right" )
63
+ }
64
+ row {
65
+ cell(" bottom left" )
66
+ cell(" bottom right" )
66
67
}
68
+ }
67
69
```
68
70
The idea is to define classes for ` Table ` and ` Row ` that allow the
69
71
addition of elements via ` add ` :
70
72
``` scala
71
- class Table :
72
- val rows = new ArrayBuffer [Row ]
73
- def add (r : Row ): Unit = rows += r
74
- override def toString = rows.mkString(" Table(" , " , " , " )" )
73
+ import scala .collection .mutable .ArrayBuffer
74
+
75
+ class Table :
76
+ val rows = new ArrayBuffer [Row ]
77
+ def add (r : Row ): Unit = rows += r
78
+ override def toString = rows.mkString(" Table(" , " , " , " )" )
75
79
76
- class Row :
77
- val cells = new ArrayBuffer [Cell ]
78
- def add (c : Cell ): Unit = cells += c
79
- override def toString = cells.mkString(" Row(" , " , " , " )" )
80
+ class Row :
81
+ val cells = new ArrayBuffer [Cell ]
82
+ def add (c : Cell ): Unit = cells += c
83
+ override def toString = cells.mkString(" Row(" , " , " , " )" )
80
84
81
- case class Cell (elem : String )
85
+ case class Cell (elem : String )
82
86
```
83
87
Then, the ` table ` , ` row ` and ` cell ` constructor methods can be defined
84
88
with context function types as parameters to avoid the plumbing boilerplate
85
89
that would otherwise be necessary.
86
90
``` scala
87
- def table (init : Table ?=> Unit ) =
88
- given t : Table = Table ()
89
- init
90
- t
91
-
92
- def row (init : Row ?=> Unit )(using t : Table ) =
93
- given r : Row = Row ()
94
- init
95
- t.add(r)
96
-
97
- def cell (str : String )(using r : Row ) =
98
- r.add(new Cell (str))
91
+ def table (init : Table ?=> Unit ) =
92
+ given t : Table = Table ()
93
+ init
94
+ t
95
+
96
+ def row (init : Row ?=> Unit )(using t : Table ) =
97
+ given r : Row = Row ()
98
+ init
99
+ t.add(r)
100
+
101
+ def cell (str : String )(using r : Row ) =
102
+ r.add(new Cell (str))
99
103
```
100
104
With that setup, the table construction code above compiles and expands to:
101
105
``` scala
102
- table { ($t : Table ) ?=>
103
-
104
- row { ($r : Row ) ?=>
105
- cell(" top left" )(using $r)
106
- cell(" top right" )(using $r)
107
- }(using $t)
108
-
109
- row { ($r : Row ) ?=>
110
- cell(" bottom left" )(using $r)
111
- cell(" bottom right" )(using $r)
112
- }(using $t)
113
- }
106
+ table { ($t : Table ) ?=>
107
+
108
+ row { ($r : Row ) ?=>
109
+ cell(" top left" )(using $r)
110
+ cell(" top right" )(using $r)
111
+ }(using $t)
112
+
113
+ row { ($r : Row ) ?=>
114
+ cell(" bottom left" )(using $r)
115
+ cell(" bottom right" )(using $r)
116
+ }(using $t)
117
+ }
114
118
```
115
119
## Example: Postconditions
116
120
@@ -131,12 +135,18 @@ import PostConditions.{ensuring, result}
131
135
132
136
val s = List (1 , 2 , 3 ).sum.ensuring(result == 6 )
133
137
```
134
- ** Explanations** : We use a context function type ` WrappedResult[T] ?=> Boolean `
138
+ ### Explanation
139
+
140
+ We use a context function type ` WrappedResult[T] ?=> Boolean `
135
141
as the type of the condition of ` ensuring ` . An argument to ` ensuring ` such as
136
142
` (result == 6) ` will therefore have a given of type ` WrappedResult[T] ` in
137
- scope to pass along to the ` result ` method. ` WrappedResult ` is a fresh type, to make sure
143
+ scope to pass along to the ` result ` method.
144
+
145
+ ` WrappedResult ` is a fresh type, to make sure
138
146
that we do not get unwanted givens in scope (this is good practice in all cases
139
- where context parameters are involved). Since ` WrappedResult ` is an opaque type alias, its
147
+ where context parameters are involved).
148
+
149
+ Since ` WrappedResult ` is an opaque type alias, its
140
150
values need not be boxed, and since ` ensuring ` is added as an extension method, its argument
141
151
does not need boxing either. Hence, the implementation of ` ensuring ` is close in efficiency to the best possible code one could write by hand:
142
152
0 commit comments