Skip to content

Commit 9c99561

Browse files
committed
Complete reference documentation
1 parent 1bbb949 commit 9c99561

File tree

6 files changed

+445
-7
lines changed

6 files changed

+445
-7
lines changed

docs/docs/internals/syntax.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ ClassQualifier ::= ‘[’ id ‘]’
117117

118118
### Types
119119
```ebnf
120-
Type ::= [FunArgMods] FunArgTypes ‘=>’ Type Function(ts, t)
120+
Type ::= [FunArgMods | ‘.’] FunArgTypes ‘=>’ Type Function(ts, t)
121121
| HkTypeParamClause ‘=>’ Type TypeLambda(ps, t)
122122
| MatchType
123123
| InfixType
@@ -156,7 +156,7 @@ TypeParamBounds ::= TypeBounds {‘<%’ Type} {‘:’ Type}
156156

157157
### Expressions
158158
```ebnf
159-
Expr ::= [FunArgMods] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr)
159+
Expr ::= [FunArgMods | ‘.’] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr)
160160
| Expr1
161161
BlockResult ::= [FunArgMods] FunParams ‘=>’ Block
162162
| Expr1
@@ -198,6 +198,7 @@ SimpleExpr1 ::= Literal
198198
| SimpleExpr ‘.’ id Select(expr, id)
199199
| SimpleExpr (TypeArgs | NamedTypeArgs) TypeApply(expr, args)
200200
| SimpleExpr1 ArgumentExprs Apply(expr, args)
201+
| SimpleExpr1 ‘.’ ParArgumentExprs
201202
| XmlExpr
202203
ExprsInParens ::= ExprInParens {‘,’ ExprInParens}
203204
ExprInParens ::= PostfixExpr ‘:’ Type
@@ -266,6 +267,7 @@ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] |
266267
267268
ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [FunArgMods] ClsParams ‘)’]
268269
ClsParamClause ::= [nl] ‘(’ [ClsParams] ‘)’
270+
| ‘.’ ‘(’ ClsParams ‘)’
269271
ClsParams ::= ClsParam {‘,’ ClsParam}
270272
ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var
271273
[{Modifier} (‘val’ | ‘var’) | ‘inline’] Param
@@ -274,12 +276,10 @@ Param ::= id ‘:’ ParamType [‘=’ Expr]
274276
275277
DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’]
276278
DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’
279+
| ‘.’ ‘(’ DefParams ‘)’
277280
ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’
278281
DefParams ::= DefParam {‘,’ DefParam}
279282
DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id.
280-
281-
WitnessParams ::= WitnessParam {‘,’ WitnessParam}
282-
WitnessParam ::= DefParam | ParamType
283283
```
284284

285285
### Bindings and Imports

docs/docs/reference/witnesses.md renamed to docs/docs/reference/witnesses/motivation.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,44 @@
11
---
22
layout: doc-page
3-
title: "Witnesses"
3+
title: "Motivation"
44
---
55

6-
Witnesses provide a concise and uniform way to define implicit values such as type class instances. For instance, here is a type class `Ord` and two witnesses implementing it for integers and ordered lists:
6+
### Critique
7+
8+
Scala's implicits are its most distinguished feature. They are _the_ fundamental way to abstract over context. They represent a single concept with an extremely varied number of use cases, among them: implementing type classes, establishing context, dependency injection, expressing capabilities, computing new types and proving relationships between them.
9+
10+
At the same time, implicits are also a controversal feature. I believe there are several reasons for this.
11+
12+
First, being very powerful, implicits are easily over-used and mis-used. This observation holds in almomst all cases when we talk about _implicit conversions_, which, even though conceptually different, share the same syntax with other implicit definitions. For instance,
13+
regarding the two definitions
14+
15+
implicit def i1(implicit x: T): C[T] = ...
16+
implicit def i2(x: T): C[T] = ...
17+
18+
the first of these is a conditional implicit _value_, the second an implicit _conversion_. Conditional implicit values are a cornerstone for expressing type classes, whereas most applications of implicit conversions have turned out to be of dubious value. The problem is that many newcomers to the language start with defining implicit conversions since they are easy to understand and seem powerful and convenient. Scala 3 will put under a language flag both definitions and applications of "undisciplined" implicit conversions between types defined elsewhere. This is a useful step to push back against overuse of implicit conversions. But the problem remains that syntactically, conversions and values just look too similar for comfort.
19+
20+
Second, implicits pose challenges for tooling. The set of available implicits depends on context, so command completion has to take context into account. This is feasible in an IDE but docs like ScalaDoc that are based static web pages can only provide an approximation. Another problem is that failed implicit searches often give very unspecific error messages, in particular if some deep recursion of a failed implicit search has failed. The dotty compiler implements some improvements in this case, but further progress would be desirable.
21+
22+
Third, the syntax of implicit definitions is maybe a bit too minimal. It consists of a single modifier, `implicit`, that can be attached to a large number of language constructs. A problem with this for newcomers is that it often conveys mechanism better than intent. For instance, a typeclass instance is an implicit object or val if unconditional and an implicit def with implicit parameters if conditional. This describes precisely what the implicit definitions translate to -- just drop the `implicit` modifier, and that's it! But the cues that define intent are rather indirect and can be easily misread, as demonstrated by the definitions of `i1` and `i2` above.
23+
24+
Fourth, the syntax of implicit parameters has also some shortcomings. It starts with the position of `implicit` as a pseudo-modifier that applies to a whole parameter section instead of a single parameter. This represents an irregular case wrt to the rest of Scala's syntax. Furthermore, while implicit _parameters_ are designated specifically, arguments are not. Passing an argument to an implicit parameter looks like a regular application `f(arg)`. This is problematic because it means there can be confusion regarding what parameter gets instantiated in a call. For instance, in
25+
```scala
26+
def currentMap(implicit ctx: Context): Map[String, Int]
27+
```
28+
one cannot write `currentMap("abc")` since the string "abc" is taken as explicit argument to the implicit `ctx` parameter. One has to write `currentMap.apply("abc")` instead, which is awkward and irregular. For the same reason, a method definition can only have one implicit parameter section and it must always come last. This restriction not only reduces orthogonality, but also prevents some useful program constructs, such as a method with a regular parameter type that depends on an implicit value. Finally, it's also a bit annoying that implicit parameters must have a name, even though in most cases that name is never referenced.
29+
30+
None of the shortcomings is fatal, after all implicits are very widely used, and many libraries and applications rely on them. But together, they make code using implicits more cumbersome and less clear than it could be.
31+
32+
Can implicit function types help? Implicit function types allow to abstract over implicit parameterization. They are a key part of the program to make as many aspects of methods as possible first class. Implicit function types can avoid much of the repetition in programs that use implicits widely. But they do not directly address the issues mentioned here.
33+
34+
### Alternative Design
35+
36+
`implicit` is a modifier that gets attached to various constructs. I.e. we talk about implicit vals, defs, objects, parameters, or arguments. This conveys mechanism rather than intent. What _is_ the intent that we want to convey? Ultimately it's "trade types for terms". The programmer specifies a type and the compiler fills in the term matching that type automatically. So the concept we are after would serve to express definitions that provide the canonical instances for certain types.
37+
38+
I believe a good name for is concept is _witness_. A term is a witness for a type by defining an implicit instance of this type. It's secondary whether this
39+
instance takes the form of a `val` or `object` or whether it is a method. It would be better to have a uniform syntax for all of these kinds of instances. The next sections elaborate
40+
such an alternative design.
41+
742

843
```scala
944
trait Ord[T] {
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
---
2+
layout: doc-page
3+
title: "Witness Parameters and Arguments"
4+
---
5+
6+
Witness parameters is a new syntax to define implicit parameters. Unlike traditional implicit parameters, witness parameters come with specific syntax for applications, which mirrors the parameter syntax.
7+
8+
A witness parameter list starts with a dot ‘.’ and is followed by a normal parameter list. Analogously, a witness argument list also starts with a ‘.’ and is followed by a normal argument list. Example:
9+
```scala
10+
def maximum[T](xs: List[T])
11+
.(cmp: Ord[T]): T =
12+
xs.reduceLeft((x, y) => if (x < y) y else x)
13+
14+
def decending[T].(asc: Ord[T]): Ord[T] = new Ord[T] {
15+
def compareTo(this x: Int)(y: Int) = asc.compareTo(y)(x)
16+
}
17+
18+
def minimum[T](xs: List[T]).(cmp: Ord[T]) =
19+
maximum(xs).(descending)
20+
```
21+
The example shows three methods that each have a witness parameter list for `Ord[T]`.
22+
The `minimum` method's right hand side contains witness arguments `.(descending)`.
23+
As is the case for implicit arguments, witness arguments can be left out. For instance,
24+
given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:)
25+
```scala
26+
maximum(xs)
27+
maximum(xs).(descending)
28+
maximum(xs).(descending.(IntOrd))
29+
```
30+
Unlike for implicit parameters, witness arguments must be passed using the `.(...)` syntax. So the expression `maximum(xs)(descending)` would give a type error.
31+
32+
Witness parameters translate straightforwardly to implicit parameters. Here are the previous three method definitions again, this time formulated using implicit parameters.
33+
```scala
34+
def maximum[T](xs: List[T])
35+
(implicit cmp: Ord[T]): T =
36+
xs.reduceLeft((x, y) => if (x < y) y else x)
37+
38+
def descending[T](implicit asc: Ord[T]): Ord[T] = new Ord[T] {
39+
def compareTo(this x: T)(y: T) = asc.compareTo(y)(x)
40+
}
41+
42+
def minimum[T](xs: List[T])(implicit cmp: Ord[T]) =
43+
maximum(xs)(descending)
44+
```
45+
46+
## Anonymous Witness Parameters
47+
48+
The `<name> :` part of a witness parameter can be left out. For instance, the `minimum` and `maximum` method definitions could be abbreviated to
49+
```scala
50+
def maximum[T](xs: List[T]).(_: Ord[T]): T =
51+
xs.reduceLeft((x, y) => if (x < y) y else x)
52+
53+
def minimum[T](xs: List[T]).(_: Ord[T]) =
54+
maximum(xs).(descending)
55+
```
56+
57+
## Summoning a Witness
58+
59+
The `implicitly` method, defined in `Predef` computes an implicit value for a given type. Keeping with the "witness" terminology, it seems apt to inroduce the name `summon` for this operation. So `summon[T]` summons a witness for `T`, in the same way as `implicitly[T]`.
60+
The definition of `summon` is straightforward:
61+
```scala
62+
def summon[T].(x: T) = x
63+
```
64+
65+
## Implicit Closures and Function Types
66+
67+
A period ‘.’ in front of a parameter list also marks implicit closures and implicit function types. Examples for types:
68+
```scala
69+
.Context => T
70+
.A => .B => T
71+
.(A, B) => T
72+
.(x: A, y: B) => T
73+
```
74+
Examples for closures:
75+
```scala
76+
.ctx => ctx.value
77+
.(ctx: Context) => ctx.value
78+
.(a: A, b: B) => t
79+
```
80+
81+
## Syntax
82+
83+
Here is the new syntax for witness definitions, parameters and arguments, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html).
84+
```
85+
TmplDef ::= ...
86+
| ‘witness’ WitnessDef
87+
WitnessDef ::= [id] WitnessClauses [‘for’ [ConstrApps]] TemplateBody
88+
| [id] WitnessClauses [‘for’ Type] ‘=’ Expr
89+
| id WitnessClauses ‘for’ Type
90+
WitnessClauses ::= [DefTypeParamClause] [‘with’ DefParams]
91+
92+
ClsParamClause ::= ...
93+
| ‘.’ ‘(’ ClsParams ‘)’
94+
DefParamClause ::= ...
95+
| ‘.’ ‘(’ DefParams ‘)’
96+
Type ::= ...
97+
| ‘.’ FunArgTypes ‘=>’ Type
98+
Expr ::= ...
99+
| ‘.’ FunParams ‘=>’ Expr
100+
101+
SimpleExpr1 ::= ...
102+
| SimpleExpr1 ‘.’ ParArgumentExprs
103+
```
104+
105+
## More Examples
106+
107+
Semigroups and monoids:
108+
```scala
109+
trait SemiGroup[T] {
110+
def combine(this x: T)(y: T): T
111+
}
112+
trait Monoid[T] extends SemiGroup[T] {
113+
def unit: T
114+
}
115+
116+
witness for Monoid[String] {
117+
def combine(this x: String)(y: String): String = x.concat(y)
118+
def unit: String = ""
119+
}
120+
121+
def sum[T: Monoid](xs: List[T]): T =
122+
xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_))
123+
```
124+
Functors and monads:
125+
```scala
126+
trait Functor[F[_]] {
127+
def map[A, B](this x: F[A])(f: A => B): F[B]
128+
}
129+
130+
trait Monad[F[_]] extends Functor[F] {
131+
def flatMap[A, B](this x: F[A])(f: A => F[B]): F[B]
132+
def map[A, B](this x: F[A])(f: A => B) = x.flatMap(f `andThen` pure)
133+
134+
def pure[A](x: A): F[A]
135+
}
136+
137+
witness ListMonad for Monad[List] {
138+
def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] =
139+
xs.flatMap(f)
140+
def pure[A](x: A): List[A] =
141+
List(x)
142+
}
143+
144+
witness ReaderMonad[Ctx] for Monad[[X] => Ctx => X] {
145+
def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B =
146+
ctx => f(r(ctx))(ctx)
147+
def pure[A](x: A): Ctx => A =
148+
ctx => x
149+
}
150+
```
151+
Implementing postconditions via `ensuring`:
152+
```scala
153+
object PostConditions {
154+
opaque type WrappedResult[T] = T
155+
156+
private witness WrappedResult {
157+
def apply[T](x: T): WrappedResult[T] = x
158+
def unwrap[T](this x: WrappedResult[T]): T = x
159+
}
160+
161+
def result[T].(wrapped: WrappedResult[T]): T = wrapped.unwrap
162+
163+
witness {
164+
def ensuring[T](this x: T)(condition: .WrappedResult[T] => Boolean): T = {
165+
assert(condition.(WrappedResult(x)))
166+
x
167+
}
168+
}
169+
}
170+
171+
object Test {
172+
import PostConditions._
173+
val s = List(1, 2, 3).sum.ensuring(result == 6)
174+
}
175+
```

0 commit comments

Comments
 (0)