From aa0429a575ea78e31cee486992724fd4459e849d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 2 Mar 2017 17:52:06 +0100 Subject: [PATCH 1/2] Coherent type classes A simple implementation of a Coherent trait which avoids ambiguity errors for goals that derive from it. --- .../dotty/tools/dotc/core/Definitions.scala | 2 ++ .../dotty/tools/dotc/core/Denotations.scala | 2 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 3 ++- library/src/scala/typeclass/Coherent.scala | 12 +++++++++ tests/run/coherent.scala | 25 +++++++++++++++++++ 6 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 library/src/scala/typeclass/Coherent.scala create mode 100644 tests/run/coherent.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 79e97becb6dd..b0d94d162a52 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -516,6 +516,8 @@ class Definitions { def Product_productArity(implicit ctx: Context) = Product_productArityR.symbol lazy val Product_productPrefixR = ProductClass.requiredMethodRef(nme.productPrefix) def Product_productPrefix(implicit ctx: Context) = Product_productPrefixR.symbol + lazy val CoherentType: TypeRef = ctx.requiredClassRef("scala.typeclass.Coherent") + def CoherentClass(implicit ctx: Context) = CoherentType.symbol.asClass lazy val LanguageModuleRef = ctx.requiredModule("scala.language") def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl") diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 99c688d50973..28fe3427a514 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -154,7 +154,7 @@ object Denotations { /** The unique alternative of this denotation that satisfies the predicate `p`, * or NoDenotation if no satisfying alternative exists. - * @throws TypeError if there is at more than one alternative that satisfies `p`. + * @throws TypeError if there is more than one alternative that satisfies `p`. */ def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 308e6e306198..d4103bc9774c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -187,7 +187,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. * class A extends C[A] with D * class B extends C[B] with D with E * - * we approximate `A | B` by `C[A | B] with D` + * we approximate `A | B` by `C[A | B] & D` */ def orDominator(tp: Type): Type = { diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index d5afae90c078..a024210dcf2e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -807,7 +807,8 @@ trait Implicits { self: Typer => /** Convert a (possibly empty) list of search successes into a single search result */ def condense(hits: List[SearchSuccess]): SearchResult = hits match { case best :: alts => - alts find (alt => isAsGood(alt.ref, best.ref, alt.level, best.level)(ctx.fresh.setExploreTyperState)) match { + if (pt.derivesFrom(defn.CoherentClass)) best + else alts find (alt => isAsGood(alt.ref, best.ref, alt.level, best.level)(ctx.fresh.setExploreTyperState)) match { case Some(alt) => typr.println(i"ambiguous implicits for $pt: ${best.ref} @ ${best.level}, ${alt.ref} @ ${alt.level}") /* !!! DEBUG diff --git a/library/src/scala/typeclass/Coherent.scala b/library/src/scala/typeclass/Coherent.scala new file mode 100644 index 000000000000..6470a2568c1f --- /dev/null +++ b/library/src/scala/typeclass/Coherent.scala @@ -0,0 +1,12 @@ +package scala.typeclass + +/** If a type class C extends this trait, the compiler is allowed + * to assume that for every type instance of `C`, all implicit + * instances of this type instance behave the same way. + * + * Conversely, an implicit resolution of a coherent type class will + * never give an ambiguity error. If there are several candidates + * and none is more specific than the others, an arbitrary candidate + * is chosen. + */ +trait Coherent diff --git a/tests/run/coherent.scala b/tests/run/coherent.scala new file mode 100644 index 000000000000..d6891804fd27 --- /dev/null +++ b/tests/run/coherent.scala @@ -0,0 +1,25 @@ +trait Base[T] extends scala.typeclass.Coherent { + def value: T +} + +class Sub1[T](val value: T) extends Base[T] +class Sub2[T](val value: T) extends Base[T] + +object Test { + implicit class ValueDeco[T: Base](x: T) { + def value = implicitly[Base[T]].value + } + def f[T](t: T)(implicit ev1: Sub1[T], ev2: Sub2[T]) = { + // the next four lines give errors if `Base` is not declared coherent + assert(t.value == 2) + assert(implicitly[Base[T]].value == 2) + assert(implicitly[Base[T]].value == 2) + assert(t.value == 2) + } + implicit val s1: Sub1[Int] = new Sub1(2) + implicit val s2: Sub2[Int] = new Sub2(3) // This is not coherent, just used to show that first alternative will be chosen. + + def main(args: Array[String]) = { + f(1) + } +} From ef61123d762ef0365df1f7147fa8cd3623d6f5c2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 3 Mar 2017 13:54:05 +0100 Subject: [PATCH 2/2] Add example to test --- tests/run/coherent.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/run/coherent.scala b/tests/run/coherent.scala index d6891804fd27..fd50f2655c16 100644 --- a/tests/run/coherent.scala +++ b/tests/run/coherent.scala @@ -23,3 +23,12 @@ object Test { f(1) } } + +trait DL extends scala.typeclass.Coherent +trait TL extends DL +trait CL extends DL + +object Driving { + def drive(implicit dl: DL) = () + def f(implicit tl: TL, cl: CL) = drive +}