Skip to content

Commit 6005868

Browse files
committed
Update docs to given ... with syntax
For given instances: given ... For context parameters ... with ... For context functions A ?=> B
1 parent aeabb14 commit 6005868

34 files changed

+1772
-41
lines changed

docs/docs/internals/syntax.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -299,19 +299,19 @@ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] |
299299
SubtypeBounds
300300
301301
ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’]
302-
| {ClsParamClause} {GivenClsParamClause}
302+
| {ClsParamClause} {WithClsParamClause}
303303
ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’
304-
GivenClsParamClause::= ‘with’ (‘(’ (ClsParams | Types) ‘)’ | AnnotTypes)
304+
WithClsParamClause::= ‘with’ (‘(’ (ClsParams | Types) ‘)’ | AnnotTypes)
305305
ClsParams ::= ClsParam {‘,’ ClsParam}
306306
ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var
307307
[{Modifier} (‘val’ | ‘var’) | ‘inline’] Param
308308
Param ::= id ‘:’ ParamType [‘=’ Expr]
309309
| INT
310310
311311
DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [‘implicit’] DefParams ‘)’]
312-
| {DefParamClause} {GivenParamClause}
312+
| {DefParamClause} {WithParamClause}
313313
DefParamClause ::= [nl] ‘(’ DefParams ‘)’
314-
GivenParamClause ::= ‘with’ (‘(’ (DefParams | Types) ‘)’ | AnnotTypes)
314+
WithParamClause ::= ‘with’ (‘(’ (DefParams | Types) ‘)’ | AnnotTypes)
315315
DefParams ::= DefParam {‘,’ DefParam}
316316
DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id.
317317
ClosureMods ::= { ‘implicit’ | ‘given’}
@@ -391,8 +391,8 @@ ObjectDef ::= id [Template]
391391
EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template)
392392
GivenDef ::= [GivenSig] [‘_’ ‘<:’] Type ‘=’ Expr
393393
| [GivenSig] ConstrApps [TemplateBody]
394-
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause} ‘as’
395-
ExtensionDef ::= [id] ‘on’ ExtParamClause {GivenParamClause} ExtMethods
394+
GivenSig ::= [id] [DefTypeParamClause] {WithParamClause} ‘as’
395+
ExtensionDef ::= [id] ‘on’ ExtParamClause {WithParamClause} ExtMethods
396396
ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
397397
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
398398
Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
layout: doc-page
3+
title: "By-Name Context Parameters"
4+
---
5+
6+
Context parameters can be declared by-name to avoid a divergent inferred expansion. Example:
7+
8+
```scala
9+
trait Codec[T] {
10+
def write(x: T): Unit
11+
}
12+
13+
given intCodec as Codec[Int] = ???
14+
15+
given optionCodec[T] with (ev: => Codec[T]) as Codec[Option[T]] {
16+
def write(xo: Option[T]) = xo match {
17+
case Some(x) => ev.write(x)
18+
case None =>
19+
}
20+
}
21+
22+
val s = summon[Codec[Option[Int]]]
23+
24+
s.write(Some(33))
25+
s.write(None)
26+
```
27+
As is the case for a normal by-name parameter, the argument for the context parameter `ev`
28+
is evaluated on demand. In the example above, if the option value `x` is `None`, it is
29+
not evaluated at all.
30+
31+
The synthesized argument for a context parameter is backed by a local val
32+
if this is necessary to prevent an otherwise diverging expansion.
33+
34+
The precise steps for synthesizing an argument for a by-name context parameter of type `=> T` are as follows.
35+
36+
1. Create a new given of type `T`:
37+
38+
```scala
39+
given lv as T = ???
40+
```
41+
where `lv` is an arbitrary fresh name.
42+
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.
44+
45+
1. If this search succeeds with expression `E`, and `E` contains references to `lv`, replace `E` by
46+
47+
48+
```scala
49+
{ given lv as T = E; lv }
50+
```
51+
52+
Otherwise, return `E` unchanged.
53+
54+
In the example above, the definition of `s` would be expanded as follows.
55+
56+
```scala
57+
val s = summon[Test.Codec[Option[Int]]](
58+
optionCodec[Int].with(intCodec)
59+
)
60+
```
61+
62+
No local given instance was generated because the synthesized argument is not recursive.
63+
64+
### Reference
65+
66+
For more info, see [Issue #1998](https://github.com/lampepfl/dotty/issues/1998)
67+
and the associated [Scala SIP](https://docs.scala-lang.org/sips/byname-implicits.html).
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
layout: doc-page
3+
title: "Context Bounds"
4+
---
5+
6+
## Context Bounds
7+
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:
9+
```scala
10+
def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max)
11+
```
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.,
13+
```scala
14+
def f[T: C1 : C2, U: C3](x: T) with (y: U, z: V) : R
15+
```
16+
would expand to
17+
```scala
18+
def f[T, U](x: T) with (y: U, z: V) with C1[T], C2[T], C3[U]) : R
19+
```
20+
Context bounds can be combined with subtype bounds. If both are present, subtype bounds come first, e.g.
21+
```scala
22+
def g[T <: B : C](x: T): R = ...
23+
```
24+
25+
## Syntax
26+
27+
```
28+
TypeParamBounds ::= [SubtypeBounds] {ContextBound}
29+
ContextBound ::= ‘:’ Type
30+
```

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ layout: doc-page
33
title: "Context Bounds"
44
---
55

6+
**Note** The syntax described in this section is currently under revision.
7+
[Here is the new version which will be implemented in Dotty 0.22](./context-bounds-new.html).
8+
69
## Context Bounds
710

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

0 commit comments

Comments
 (0)