Skip to content

Commit

Permalink
Implement and test "replacing-implicits" chapter.
Browse files Browse the repository at this point in the history
  • Loading branch information
odersky committed Nov 16, 2018
1 parent 05405d2 commit 178bd77
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 33 deletions.
22 changes: 16 additions & 6 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2464,6 +2464,7 @@ object Parsers {

/** WitnessDef ::= [id] WitnessParams [‘for’ ConstrApps] [TemplateBody]
* | id WitnessParams ‘:’ Type ‘=’ Expr
* | id ‘:’ ‘=>’ Type ‘=’ Expr
* | id ‘=’ Expr
* WitnessParams ::= [DefTypeParamClause] {‘with’ ‘(’ [DefParams] ‘)}
*/
Expand All @@ -2478,7 +2479,7 @@ object Parsers {
}
else Nil
newLineOptWhenFollowedBy(LBRACE)
if ((name.isEmpty || parents.isEmpty) && in.token != LBRACE)
if (name.isEmpty && in.token != LBRACE)
syntaxErrorOrIncomplete(ExpectedTokenButFound(LBRACE, in.token))
var mods1 = addMod(mods, witnessMod)
val wdef =
Expand All @@ -2488,17 +2489,26 @@ object Parsers {
else TypeDef(name.toTypeName, templ)
}
else {
var byName = false
val tpt =
if (in.token == COLON) {
in.nextToken()
if (in.token == ARROW && tparams.isEmpty && vparamss.isEmpty) {
in.nextToken()
byName = true
}
toplevelTyp()
}
else TypeTree()
if (tpt.isEmpty && in.token != EQUALS)
syntaxErrorOrIncomplete(ExpectedTokenButFound(LBRACE, in.token))
val rhs =
if (in.token == EQUALS) {
in.nextToken()
expr()
}
else EmptyTree
val tpt = constrAppsToType(parents)
if (tparams.isEmpty && vparamss.isEmpty) {
mods1 |= Lazy
ValDef(name, tpt, rhs)
}
if (tparams.isEmpty && vparamss.isEmpty && !byName) ValDef(name, tpt, rhs)
else DefDef(name, tparams, vparamss, tpt, rhs)
}
finalizeDef(wdef, mods1, start)
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,9 @@ ObjectDef ::= id TemplateOpt
EnumDef ::= id ClassConstr [‘extends’ [ConstrApps]] EnumBody EnumDef(mods, name, tparams, template)
WitnessDef ::= [id] WitnessParams [‘for’ ConstrApps] [TemplateBody]
| id WitnessParams ‘:’ Type ‘=’ Expr
| id ‘:’ ‘=>’ Type ‘=’ Expr
| id ‘=’ Expr
WitnessParams ::= [DefTypeParamClause] {‘with’ ‘(’ [DefParams] ‘)}
WitnessParams ::= [DefTypeParamClause] {‘with’ ‘(’ [DefParams] ‘)'}
TemplateOpt ::= [‘extends’ Template | [nl] TemplateBody]
Template ::= ConstrApps [TemplateBody] | TemplateBody Template(constr, parents, self, stats)
ConstrApps ::= ConstrApp {‘with’ ConstrApp}
Expand Down
8 changes: 5 additions & 3 deletions docs/docs/reference/witnesses/replacing-implicits.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Further examples of alias witnesses:
```scala
witness ctx = outer.ctx
witness ctx: Context = outer.ctx
witness byNameCtx(): Context = outer.ctx
witness byNameCtx: => Context = outer.ctx
witness f[T]: C[T] = new C[T]
witness g with (ctx: Context): D = new D(ctx)
```
Expand All @@ -55,14 +55,15 @@ witness listOrd[T: Ord]: Ord[List[T]] = new ListOrd[T]
The result type of a alias witness is mandatory unless the witness definition
occurs as a statement in a block and lacks any type or value parameters. This corresponds to the same restriction for implicit vals in Dotty.

Abstract witnesses are equivalent to abstract implicit defs. Alias witnesses are equivalent to implicit defs if they are parameterized or for implicit vals otherwise. For instance, the witnesses defined so far in this section are equivalent to:
Abstract witnesses are equivalent to abstract implicit defs. Alias witnesses are equivalent to implicit defs if they are parameterized or have a `=> T` result type.
They translate to implicit vals otherwise. For instance, the witnesses defined so far in this section are equivalent to:
```scala
implicit def symDeco: SymDeco
implicit val symDeco: SymDeco = compilerSymOps

implicit val ctx = outer.ctx
implicit val ctx: Context = outer.ctx
implicit def byNameCtx(): Ctx = outer.ctx
implicit def byNameCtx: Ctx = outer.ctx
implicit def f[T]: C[T] = new C[T]
implicit def g(implicit ctx: Context): D = new D(ctx)

Expand Down Expand Up @@ -107,6 +108,7 @@ The syntax changes for this page are summarized as follows:
```
WitnessDef ::= ...
| id WitnessParams ‘:’ Type ‘=’ Expr
| id ‘:’ ‘=>’ Type ‘=’ Expr
| id ‘=’ Expr
```
In addition, the `implicit` modifier is removed together with all [productions]((http://dotty.epfl.ch/docs/internals/syntax.html) that reference it.
45 changes: 24 additions & 21 deletions docs/docs/reference/witnesses/witnesses.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ Witnesses provide a concise and uniform syntax for defining implicit values. Exa

```scala
trait Ord[T] {
def compareTo(this x: T)(y: T): Int
def < (this x: T)(y: T) = x.compareTo(y) < 0
def > (this x: T)(y: T) = x.compareTo(y) > 0
def (x: T) compareTo (y: T): Int
def (x: T) < (y: T) = x.compareTo(y) < 0
def (x: T) > (y: T) = x.compareTo(y) > 0
}

witness IntOrd for Ord[Int] {
def compareTo(this x: Int)(y: Int) =
def (x: Int) compareTo (y: Int) =
if (x < y) -1 else if (x > y) +1 else 0
}

witness ListOrd[T: Ord] for Ord[List[T]] {
def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match {
def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match {
case (Nil, Nil) => 0
case (Nil, _) => -1
case (_, Nil) => +1
Expand All @@ -32,12 +32,12 @@ witness ListOrd[T: Ord] for Ord[List[T]] {
Witness can be seen as shorthands for what is currently expressed as implicit definitions. The witnesses above could also have been formulated as implicits as follows:
```scala
implicit object IntOrd extends Ord[Int] {
def compareTo(this x: Int)(y: Int) =
def (x: Int) compareTo (y: Int) =
if (x < y) -1 else if (x > y) +1 else 0
}

class ListOrd[T: Ord] extends Ord[List[T]] {
def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match {
def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match {
case (Nil, Nil) => 0
case (Nil, _) => -1
case (_, Nil) => +1
Expand All @@ -60,14 +60,14 @@ Witnesses can also be defined without a `for` clause. A typical application is t

```scala
witness StringOps {
def longestStrings(this xs: Seq[String]): Seq[String] = {
def (xs: Seq[String]) longestStrings: Seq[String] = {
val maxLength = xs.map(_.length).max
xs.filter(_.length == maxLength)
}
}

witness ListOps {
def second[T](this xs: List[T]) = xs.tail.head
def (xs: List[T]) second[T] = xs.tail.head
}
```
Witnesses like these translate to `implicit` objects without an extends clause.
Expand All @@ -80,7 +80,7 @@ witness for Ord[Int] { ... }
witness [T: Ord] for Ord[List[T]] { ... }

witness {
def second[T](this xs: List[T]) = xs.tail.head
def (xs: List[T]) second[T] = xs.tail.head
}
```
If the name of a witness is missing, the compiler will synthesize a name from
Expand All @@ -92,11 +92,11 @@ extension method. Details remain to be specified.
A witness can depend on another witness being defined. For instance:
```scala
trait Convertible[From, To] {
def convert (this x: From): To
def (x: From) convert: To
}

witness [From, To] with (c: Convertible[From, To]) for Convertible[List[From], List[To]] {
def convert (this x: List[From]): List[To] = x.map(c.convert)
def (x: List[From]) convert: List[To] = x.map(c.convert)
}
```

Expand All @@ -105,7 +105,7 @@ The `with` clause in a witness defines required witnesses. The witness for `Conv
```scala
class Convertible_List_List_witness[From, To](implicit c: Convertible[From, To])
extends Convertible[List[From], List[To]] {
def convert (this x: List[From]): List[To] = x.map(c.convert)
def (x: List[From]) convert: List[To] = x.map(c.convert)
}
implicit def Convertible_List_List_witness[From, To](implicit c: Convertible[From, To])
: Convertible[List[From], List[To]] =
Expand All @@ -132,42 +132,45 @@ Semigroups and monoids:

```scala
trait SemiGroup[T] {
def combine(this x: T)(y: T): T
def (x: T) combine (y: T): T
}
trait Monoid[T] extends SemiGroup[T] {
def unit: T
}
object Monoid {
def apply[T: Monoid] = summon[Monoid[T]]
}

witness for Monoid[String] {
def combine(this x: String)(y: String): String = x.concat(y)
def (x: String) combine (y: String): String = x.concat(y)
def unit: String = ""
}

def sum[T: Monoid](xs: List[T]): T =
xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_))
xs.foldLeft(Monoid[T].unit)(_.combine(_))
```
Functors and monads:
```scala
trait Functor[F[_]] {
def map[A, B](this x: F[A])(f: A => B): F[B]
def (x: F[A]) map[A, B] (f: A => B): F[B]
}

trait Monad[F[_]] extends Functor[F] {
def flatMap[A, B](this x: F[A])(f: A => F[B]): F[B]
def map[A, B](this x: F[A])(f: A => B) = x.flatMap(f `andThen` pure)
def (x: F[A]) flatMap[A, B] (f: A => F[B]): F[B]
def (x: F[A]) map[A, B] (f: A => B) = x.flatMap(f `andThen` pure)

def pure[A](x: A): F[A]
}

witness ListMonad for Monad[List] {
def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] =
def (xs: List[A]) flatMap[A, B] (f: A => List[B]): List[B] =
xs.flatMap(f)
def pure[A](x: A): List[A] =
List(x)
}

witness ReaderMonad[Ctx] for Monad[[X] => Ctx => X] {
def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B =
def (r: Ctx => A) flatMap[A, B] (f: A => Ctx => B): Ctx => B =
ctx => f(r(ctx))(ctx)
def pure[A](x: A): Ctx => A =
ctx => x
Expand Down
2 changes: 2 additions & 0 deletions library/src/dotty/DottyPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ object DottyPredef {
@forceInline final def implicitly[T](implicit ev: T): T = ev

@forceInline def locally[T](body: => T): T = body

@forceInline def summon[T](implicit x: T) = x
}
55 changes: 53 additions & 2 deletions tests/pos/reference/witnesses.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ class Common {

def pure[A](x: A): F[A]
}

inline def summon[T] with (x: T) = x
}

object Witnesses extends Common {
Expand Down Expand Up @@ -99,6 +97,59 @@ object Witnesses extends Common {
class A
class B
val ab: (x: A, y: B) |=> Int = (a: A, b: B) |=> 22

trait TastyAPI {
type Symbol
trait SymDeco {
def name(this sym: Symbol): String
}
witness symDeco: SymDeco
}
object TastyImpl extends TastyAPI {
type Symbol = String
witness symDeco: SymDeco = new SymDeco {
def name(this sym: Symbol) = sym
}
}

class D[T]

class C with (ctx: Context) {
def f() = {
locally {
witness ctx = this.ctx
println(summon[Context].value)
}
locally {
lazy witness ctx = this.ctx
println(summon[Context].value)
}
locally {
witness ctx: Context = this.ctx
println(summon[Context].value)
}
locally {
witness ctx: => Context = this.ctx
println(summon[Context].value)
}
locally {
witness f[T]: D[T] = new D[T]
println(summon[D[Int]])
}
locally {
witness g with (ctx: Context): D[Int] = new D[Int]
println(summon[D[Int]])
}
}
}

class Token(str: String)

witness StringToToken for ImplicitConverter[String, Token] {
def apply(str: String): Token = new Token(str)
}

val x: Token = "if"
}

object PostConditions {
Expand Down

0 comments on commit 178bd77

Please sign in to comment.