Skip to content

Commit 4f0b005

Browse files
committed
Some more details in docs
1 parent 9c99561 commit 4f0b005

File tree

3 files changed

+34
-345
lines changed

3 files changed

+34
-345
lines changed

docs/docs/reference/witnesses/motivation.md

Lines changed: 5 additions & 330 deletions
Original file line numberDiff line numberDiff line change
@@ -35,335 +35,10 @@ Can implicit function types help? Implicit function types allow to abstract over
3535

3636
`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.
3737

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-
42-
43-
```scala
44-
trait Ord[T] {
45-
def compareTo(this x: T)(y: T): Int
46-
def < (this x: T)(y: T) = x.compareTo(y) < 0
47-
def > (this x: T)(y: T) = x.compareTo(y) > 0
48-
}
49-
50-
witness IntOrd for Ord[Int] {
51-
def compareTo(this x: Int)(y: Int) =
52-
if (x < y) -1 else if (x > y) +1 else 0
53-
}
54-
55-
witness ListOrd[T: Ord] for Ord[List[T]] {
56-
def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match {
57-
case (Nil, Nil) => 0
58-
case (Nil, _) => -1
59-
case (_, Nil) => +1
60-
case (x :: xs1, y :: ys1) =>
61-
val fst = x.compareTo(y)
62-
if (fst != 0) fst else xs1.compareTo(ys1)
63-
}
64-
}
65-
```
66-
Witness are shorthands for implicit definitions. The winesses above could also have been
67-
formulated as implicits as follows:
68-
```scala
69-
implicit object IntOrd extends Ord[Int] {
70-
def compareTo(this x: Int)(y: Int) =
71-
if (x < y) -1 else if (x > y) +1 else 0
72-
}
73-
74-
class ListOrd[T: Ord] for Ord[List[T]] {
75-
def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match {
76-
case (Nil, Nil) => 0
77-
case (Nil, _) => -1
78-
case (_, Nil) => +1
79-
case (x :: xs1, y :: ys1) =>
80-
val fst = x.compareTo(y)
81-
if (fst != 0) fst else xs1.compareTo(ys1)
82-
}
83-
}
84-
implicit def ListOrd[T: Ord]: Ord[List[T]] = new ListOrd[T]
85-
```
86-
In fact, a plausible compilation strategy would map the witnesses given above to exactly these implicit definitions.
87-
88-
### Witness Parameters
89-
90-
Witnesses can also have implicit value parameters. For instance, here is an alternative way to write the`ListOrd` witness:
91-
```scala
92-
witness ListOrd[T] with Ord[T] for Ord[List[T]] { ... }
93-
94-
def max[T](xs: List[T]) with (Coercible[T, U]): T = ...
95-
96-
```
97-
98-
### Motivation
99-
100-
Given that witnesses are only a thin veneer on top of implicits, why introduce them?
101-
There are several reasons.
102-
103-
1. Convey meaning instead of mechanism. Witnesses
104-
105-
106-
107-
trait SemiGroup[T] {
108-
def combine(this x: T)(y: T): T
109-
}
110-
trait Monoid[T] extends SemiGroup[T] {
111-
def unit: T
112-
}
113-
114-
witness StringMonoid for Monoid[String] {
115-
def combine(this x: String)(y: String): String = x.concat(y)
116-
def unit: String = ""
117-
}
118-
```
119-
120-
121-
122-
Extension methods allow one to add methods to a type after the type is defined. Example:
123-
124-
```scala
125-
case class Circle(x: Double, y: Double, radius: Double)
126-
127-
implicit object CircleOps {
128-
def circumference(this c: Circle): Double = c.radius * math.Pi * 2
129-
}
130-
```
131-
132-
`CircleOps` adds an extension method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`:
133-
134-
```scala
135-
val circle = Circle(0, 0, 1)
136-
circle.circumference
137-
```
138-
139-
Extension methods are methods that have a `this` modifier for the first parameter.
140-
They can also be invoked as plain methods. So the following holds:
141-
```scala
142-
assert(circle.circumference == CircleOps.circumference(circle))
143-
```
144-
145-
146-
147-
### Translation of Calls to Extension Methods
148-
149-
When is an extension method considered? There are two possibilities. The first (and recommended one) is by defining the extension method as a member of an implicit value. The method can then be used as an extension method wherever the implicit value is applicable. The second possibility is by making the extension method itself visible under a simple name, typically by importing it. As an example, consider an extension method `longestStrings` on `String`. We can either define it like this:
150-
151-
152-
```scala
153-
implicit object StringSeqOps1 {
154-
def longestStrings(this xs: Seq[String]) = {
155-
val maxLength = xs.map(_.length).max
156-
xs.filter(_.length == maxLength)
157-
}
158-
}
159-
```
160-
Then
161-
```scala
162-
List("here", "is", "a", "list").longestStrings
163-
```
164-
is legal everywhere `StringSeqOps1` is available as an implicit value. Alternatively, we can define `longestStrings`
165-
as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method.
166-
167-
```scala
168-
object StringSeqOps2{
169-
def longestStrings(this xs: Seq[String]) = {
170-
val maxLength = xs.map(_.length).max
171-
xs.filter(_.length == maxLength)
172-
}
173-
}
174-
import StringSeqOps2.longestStrings
175-
List("here", "is", "a", "list").longestStrings
176-
```
177-
The precise rules for resolving a selection to an extension method are as follows.
178-
179-
Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional,
180-
and where `T` is the expected type. The following two rewritings are tried in order:
181-
182-
1. The selection is rewritten to `m[Ts](e)`.
183-
2. If the first rewriting does not typecheck with expected type `T`, and there is an implicit value `i`
184-
in either the current scope or in the implicit scope of `T`, and `i` defines an extension
185-
method named `m`, then selection is expanded to `i.m[Ts](e)`.
186-
This second rewriting is attempted at the time where the compiler also tries an implicit conversion
187-
from `T` to a type containing `m`. If there is more than one way of rewriting, an ambiguity error results.
188-
189-
So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided
190-
`circle` has type `Circle` and `CircleOps` is an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`).
191-
192-
**Note**: The translation of extension methods is formulated on method calls. It is thus indepenent from the way infix operations are translated to method calls. For instamce,
193-
if `+:` was formulated as an extension method, it would still have the `this` parameter come first, even though, seen as an operator, `+:` is right-binding:
194-
```scala
195-
def +: [T](this xs: Seq[T))(x: T): Seq[T]
196-
```
197-
198-
### Generic Extensions
199-
200-
The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible
201-
to extend a generic type by adding type parameters to an extension method:
202-
203-
```scala
204-
implicit object ListOps {
205-
def second[T](this xs: List[T]) = xs.tail.head
206-
}
207-
```
208-
209-
or:
210-
211-
212-
```scala
213-
implicit object ListListOps {
214-
def flattened[T](this xs: List[List[T]]) = xs.foldLeft[List[T]](Nil)(_ ++ _)
215-
}
216-
```
217-
218-
As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the parameter clause that precedes the defined method name.
219-
220-
### A Larger Example
221-
222-
As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extensions to provide a zero-overhead abstraction.
223-
224-
```scala
225-
object PostConditions {
226-
opaque type WrappedResult[T] = T
227-
228-
private object WrappedResult {
229-
def wrap[T](x: T): WrappedResult[T] = x
230-
def unwrap[T](x: WrappedResult[T]): T = x
231-
}
232-
233-
def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er)
234-
235-
implicit object Ensuring {
236-
def ensuring[T](this x: T)(condition: implicit WrappedResult[T] => Boolean): T = {
237-
implicit val wrapped = WrappedResult.wrap(x)
238-
assert(condition)
239-
x
240-
}
241-
}
242-
}
243-
244-
object Test {
245-
import PostConditions._
246-
val s = List(1, 2, 3).sum.ensuring(result == 6)
247-
}
248-
```
249-
**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean`
250-
as the type of the condition of `ensuring`. An argument condition to `ensuring` such as
251-
`(result == 6)` will therefore have an implicit value of type `WrappedResult[T]` in scope
252-
to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure that we do not get unwanted implicits in scope (this is good practice in all cases where implicit 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 as about as efficient as the best possible code one could write by hand:
253-
254-
{ val result = List(1, 2, 3).sum
255-
assert(result == 6)
256-
result
257-
}
258-
259-
### Rules for Overriding Extension Methods
260-
261-
Extension methods may override only extension methods and can be overridden only by extension methods.
262-
263-
### Extension Methods and TypeClasses
264-
265-
The rules for expanding extension methods make sure that they work seamlessly with typeclasses. For instance, consider `SemiGroup` and `Monoid`.
266-
```scala
267-
// Two typeclasses:
268-
trait SemiGroup[T] {
269-
def combine(this x: T)(y: T): T
270-
}
271-
trait Monoid[T] extends SemiGroup[T] {
272-
def unit: T
273-
}
274-
275-
// An instance declaration:
276-
implicit object StringMonoid extends Monoid[String] {
277-
def combine(this x: String)(y: String): String = x.concat(y)
278-
def unit: String = ""
279-
}
280-
281-
// Abstracting over a typeclass with a context bound:
282-
def sum[T: Monoid](xs: List[T]): T =
283-
xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_))
284-
```
285-
In the last line, the call to `_.combine(_)` expands to `(x1, x2) => x1.combine(x2)`,
286-
which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the implicit
287-
evidence parameter summoned by the context bound `[T: Monoid]`. This works since
288-
extension methods apply everywhere their enclosing object is available as an implicit.
289-
290-
### Generic Extension Classes
291-
292-
As another example, consider implementations of an `Ord` type class with a `minimum` value:
293-
```scala
294-
trait Ord[T] {
295-
def compareTo(this x: T)(y: T): Int
296-
def < (this x: T)(y: T) = x.compareTo(y) < 0
297-
def > (this x: T)(y: T) = x.compareTo(y) > 0
298-
val minimum: T
299-
}
300-
301-
implicit object IntOrd extends Ord[Int] {
302-
def compareTo(this x: Int)(y: Int) =
303-
if (x < y) -1 else if (x > y) +1 else 0
304-
val minimum = Int.MinValue
305-
}
306-
307-
implicit class ListOrd[T: Ord] extends Ord[List[T]] {
308-
def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match {
309-
case (Nil, Nil) => 0
310-
case (Nil, _) => -1
311-
case (_, Nil) => +1
312-
case (x :: xs1, y :: ys1) =>
313-
val fst = x.compareTo(y)
314-
if (fst != 0) fst else xs1.compareTo(ys1)
315-
}
316-
val minimum: List[T] = Nil
317-
}
318-
319-
def max[T: Ord](x: T, y: T): T = if (x < y) y else x
320-
321-
def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _))
322-
```
323-
The `ListOrd` class is generic - it works for any type argument `T` that is itself an instance of `Ord`. In current Scala, we could not define `ListOrd` as an implicit class since implicit classes can only define implicit converions that take exactly one non-implicit value parameter. We propose to drop this requirement and to also allow implicit classes without any value parameters, or with only implicit value parameters. The generated implicit method would in each case follow the signature of the class. That is, for `ListOrd` we'd generate the method:
324-
```scala
325-
implicit def ListOrd[T: Ord]: ListOrd[T] = new ListOrd[T]
326-
```
327-
328-
### Higher Kinds
329-
330-
Extension methods generalize to higher-kinded types without requiring special provisions. Example:
331-
332-
```scala
333-
trait Functor[F[_]] {
334-
def map[A, B](this x: F[A])(f: A => B): F[B]
335-
}
336-
337-
trait Monad[F[_]] extends Functor[F] {
338-
def flatMap[A, B](this x: F[A])(f: A => F[B]): F[B]
339-
def map[A, B](this x: F[A])(f: A => B) = x.flatMap(f `andThen` pure)
340-
341-
def pure[A](x: A): F[A]
342-
}
343-
344-
implicit object ListMonad extends Monad[List] {
345-
def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] =
346-
xs.flatMap(f)
347-
def pure[A](x: A): List[A] =
348-
List(x)
349-
}
350-
351-
implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] {
352-
def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B =
353-
ctx => f(r(ctx))(ctx)
354-
def pure[A](x: A): Ctx => A =
355-
ctx => x
356-
}
357-
```
358-
### Syntax
359-
360-
The required syntax extension just adds one clause for extension methods relative
361-
to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md).
362-
```
363-
DefSig ::= id [DefTypeParamClause] [ExtParamClause] DefParamClauses
364-
ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’
365-
```
366-
367-
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 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.
36839

40+
The next sections elaborate such an alternative design. It consists of two proposals which are independent of each other:
36941

42+
- A proposal to replace implicit _definitions_ by [witness definitions](./witnesses.html)
43+
.
44+
- A proposal for a [new syntax](./witness-params.html) of implicit _parameters_ and their _arguments_.

0 commit comments

Comments
 (0)