From 7111961b3ace79389816afc752accf97a0061d09 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 10 Dec 2018 13:54:35 +0100 Subject: [PATCH 01/55] Parser polishings --- .../dotty/tools/dotc/parsing/Parsers.scala | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 82abd29e4dbc..aa357de7e709 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1963,30 +1963,27 @@ object Parsers { */ def typeParamClause(ownerKind: ParamOwner.Value): List[TypeDef] = inBrackets { def typeParam(): TypeDef = { - val isConcreteOwner = ownerKind == ParamOwner.Class || ownerKind == ParamOwner.Def + val isAbstractOwner = ownerKind == ParamOwner.Type || ownerKind == ParamOwner.TypeParam val start = in.offset val mods = annotsAsMods() | { if (ownerKind == ParamOwner.Class) Param | PrivateLocal else Param } | { - if (ownerKind != ParamOwner.Def) - if (isIdent(nme.raw.PLUS)) { in.nextToken(); Covariant } - else if (isIdent(nme.raw.MINUS)) { in.nextToken(); Contravariant } - else EmptyFlags + if (ownerKind == ParamOwner.Def) EmptyFlags + else if (isIdent(nme.raw.PLUS)) { in.nextToken(); Covariant } + else if (isIdent(nme.raw.MINUS)) { in.nextToken(); Contravariant } else EmptyFlags } atSpan(start, nameStart) { val name = - if (isConcreteOwner || in.token != USCORE) ident().toTypeName - else { + if (isAbstractOwner && in.token == USCORE) { in.nextToken() WildcardParamName.fresh().toTypeName } + else ident().toTypeName val hkparams = typeParamClauseOpt(ParamOwner.TypeParam) - val bounds = - if (isConcreteOwner) typeParamBounds(name) - else typeBounds() + val bounds = if (isAbstractOwner) typeBounds() else typeParamBounds(name) TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) } } @@ -2382,7 +2379,7 @@ object Parsers { /** TmplDef ::= ([`case'] ‘class’ | trait’) ClassDef * | [`case'] `object' ObjectDef - * | `enum' EnumDef + * | ‘enum’ EnumDef */ def tmplDef(start: Int, mods: Modifiers): Tree = { in.token match { From b1fe4838f5c6361c5e7270621d92a1af74bcbfb2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 17 Oct 2018 15:18:18 +0200 Subject: [PATCH 02/55] Implement witnesses Missing: anonymous witnesses are parsed but not implemented afterwards. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 13 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 + .../dotty/tools/dotc/parsing/Parsers.scala | 57 +++++++- .../src/dotty/tools/dotc/parsing/Tokens.scala | 5 +- docs/docs/internals/syntax.md | 5 + tests/run/witnesses.check | 0 tests/run/witnesses.scala | 125 ++++++++++++++++++ 7 files changed, 202 insertions(+), 5 deletions(-) create mode 100644 tests/run/witnesses.check create mode 100644 tests/run/witnesses.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 493a515d1acc..f17f2c7e0637 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -341,6 +341,7 @@ object desugar { val isCaseClass = mods.is(Case) && !mods.is(Module) val isCaseObject = mods.is(Case) && mods.is(Module) val isImplicit = mods.is(Implicit) + val isWitness = isImplicit && mods.mods.exists(_.isInstanceOf[Mod.Witness]) val isEnum = mods.isEnumClass && !mods.is(Module) def isEnumCase = mods.isEnumCase val isValueClass = parents.nonEmpty && isAnyVal(parents.head) @@ -445,7 +446,15 @@ object desugar { } // new C[Ts](paramss) - lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef) + lazy val creatorExpr = { + val vparamss = constrVparamss match { + case (vparam :: _) :: _ if vparam.mods.is(Implicit) => // add a leading () to match class parameters + Nil :: constrVparamss + case _ => + constrVparamss + } + New(classTypeRef, vparamss.nestedMap(refOfDef)) + } val copiedAccessFlags = if (ctx.scala2Setting) EmptyFlags else AccessFlags @@ -660,7 +669,7 @@ object desugar { ctx.error(ImplicitCaseClass(cdef), cdef.sourcePos) Nil } - else if (arity != 1) { + else if (arity != 1 && !isWitness) { ctx.error(ImplicitClassPrimaryConstructorArity(), cdef.sourcePos) Nil } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index ea562d3d77cb..ff602bc3e7dc 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -149,6 +149,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Inline()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Inline) case class Enum()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Enum) + + case class Witness()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Implicit) } /** Modifiers and annotations for definitions diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index aa357de7e709..e16c098d7bdd 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1211,7 +1211,7 @@ object Parsers { } val finalizer = - if (in.token == FINALLY) { accept(FINALLY); expr() } + if (in.token == FINALLY) { in.nextToken(); expr() } else { if (handler.isEmpty) warning( EmptyCatchAndFinallyBlock(body), @@ -2380,6 +2380,7 @@ object Parsers { /** TmplDef ::= ([`case'] ‘class’ | trait’) ClassDef * | [`case'] `object' ObjectDef * | ‘enum’ EnumDef + * | ‘witness’ WitnessDef */ def tmplDef(start: Int, mods: Modifiers): Tree = { in.token match { @@ -2395,6 +2396,8 @@ object Parsers { objectDef(start, posMods(start, mods | Case | Module)) case ENUM => enumDef(start, mods, atSpan(in.skipToken()) { Mod.Enum() }) + case WITNESS => + witnessDef(start, mods, atSpan(in.skipToken()) { Mod.Witness() }) case _ => syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition()) EmptyTree @@ -2490,6 +2493,42 @@ object Parsers { Template(constr, parents, Nil, EmptyValDef, Nil) } + /** WitnessDef ::= [id] [DefTypeParamClause] [‘for’ [ConstrApps]] TemplateBody + * | id [DefTypeParamClause] ‘for’ Type [‘=’ Expr] + */ + def witnessDef(start: Offset, mods: Modifiers, witnessMod: Mod) = atPos(start, nameStart) { + val name = if (isIdent) ident() else EmptyTermName + val tparams = typeParamClauseOpt(ParamOwner.Def) + val (vparamss, _) = paramClauses() + for (vparams <- vparamss; vparam <- vparams) + if (!vparam.mods.is(Implicit)) syntaxError("witness can only have implicit parameters", vparam.pos) + val parents = + if (in.token == FOR) { + in.nextToken() + tokenSeparated(WITH, constrApp) + } + else Nil + newLineOptWhenFollowedBy(LBRACE) + if ((name.isEmpty || parents.isEmpty) && in.token != LBRACE) + syntaxErrorOrIncomplete(ExpectedTokenButFound(LBRACE, in.token)) + val wdef = + if (in.token == LBRACE) { + val templ = templateBodyOpt(makeConstructor(tparams, vparamss), parents, isEnum = false) + if (tparams.isEmpty && vparamss.isEmpty) ModuleDef(name, templ) + else TypeDef(name.toTypeName, templ) + } + else { + val rhs = + if (in.token == EQUALS) { + in.nextToken() + expr() + } + else EmptyTree + DefDef(name, tparams, vparamss, constrAppsToType(parents), rhs) + } + finalizeDef(wdef, addMod(mods, witnessMod), start) + } + /* -------- TEMPLATES ------------------------------------------- */ /** ConstrApp ::= SimpleType {ParArgumentExprs} @@ -2501,6 +2540,22 @@ object Parsers { else t } + def constrAppsToType(apps: List[Tree]): Tree = { + def constrType(app: Tree): Tree = app match { + case Apply(app1, _) => + syntaxError(i"illegal type of witness alias: $app", app.pos) + constrType(app1) + case Select(app1, nme.CONSTRUCTOR) => constrType(app1) + case New(app1) => app1 + case _ => TypeTree() + } + if (apps.isEmpty) TypeTree() + else { + val tpes = apps.map(constrType) + tpes.filterNot(_.isInstanceOf[TypeTree]).reduce(AndTypeTree) + } + } + /** ConstrApps ::= ConstrApp {‘with’ ConstrApp} (to be deprecated in 3.1) * | ConstrApp {‘,’ ConstrApp} */ diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index b9afcf03aa71..df81b6f18a43 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -178,6 +178,7 @@ object Tokens extends TokensCommon { final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate final val ENUM = 62; enter(ENUM, "enum") final val ERASED = 63; enter(ERASED, "erased") + final val WITNESS = 64; enter(WITNESS, "witness") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -198,7 +199,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords: TokenSet = tokenRange(IF, ERASED) + final val alphaKeywords: TokenSet = tokenRange(IF, WITNESS) final val symbolicKeywords: TokenSet = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens: TokenSet = tokenRange(COMMA, VIEWBOUND) final val keywords: TokenSet = alphaKeywords | symbolicKeywords @@ -221,7 +222,7 @@ object Tokens extends TokensCommon { final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) - final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE) + final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE, WITNESS) final val defIntroTokens: TokenSet = templateIntroTokens | dclIntroTokens diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 8d16b95b48a2..07dd8887e662 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -360,11 +360,16 @@ DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef | ‘enum’ EnumDef + | ‘witness’ WitnessDef ClassDef ::= id ClassConstr [Template] ClassDef(mods, name, tparams, templ) ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template) +WitnessDef ::= [id] [DefTypeParamClause] DefParamClauses + [‘for’ [ConstrApps]] TemplateBody + | id [DefTypeParamClause] DefParamClauses + [‘for’ Type] [‘=’ Expr] Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats) InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] ConstrApps ::= ConstrApp {‘with’ ConstrApp} diff --git a/tests/run/witnesses.check b/tests/run/witnesses.check new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/run/witnesses.scala b/tests/run/witnesses.scala new file mode 100644 index 000000000000..f0adab713dee --- /dev/null +++ b/tests/run/witnesses.scala @@ -0,0 +1,125 @@ +object Test extends App { + + implicit object O { + def em(this x: Int): Boolean = x > 0 + } + + assert(1.em == O.em(1)) + + case class Circle(x: Double, y: Double, radius: Double) + + witness CircleOps { + def circumference(this c: Circle): Double = c.radius * math.Pi * 2 + } + + val circle = new Circle(1, 1, 2.0) + + assert(circle.circumference == CircleOps.circumference(circle)) + + witness StringOps { + def longestStrings(this xs: Seq[String]): Seq[String] = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } + } + val names = List("hi", "hello", "world") + assert(names.longestStrings == List("hello", "world")) + + witness SeqOps { + def second[T](this xs: Seq[T]) = xs.tail.head + } + + assert(names.longestStrings.second == "world") + + witness ListListOps { + def flattened[T](this xs: List[List[T]]) = xs.foldLeft[List[T]](Nil)(_ ++ _) + } + + assert(List(names, List("!")).flattened == names :+ "!") + assert(Nil.flattened == Nil) + + trait SemiGroup[T] { + def combine(this x: T)(y: T): T + } + trait Monoid[T] extends SemiGroup[T] { + def unit: T + } + + // An instance declaration: + witness StringMonoid for Monoid[String] { + def combine(this x: String)(y: String): String = x.concat(y) + def unit: String = "" + } + + // Abstracting over a typeclass with a context bound: + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) + + println(sum(names)) + + 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 + val minimum: T + } + + witness for Ord[Int] { + def compareTo(this x: Int)(y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + val minimum = Int.MinValue + } + + witness ListOrd[T: Ord] for Ord[List[T]] { + def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } + val minimum: List[T] = Nil + } + + def max[T: Ord](x: T, y: T): T = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) + + println(max(List[Int]())) + println(max(List(1, 2, 3))) + + println(max(List(1, 2, 3), List(2))) + + trait Functor[F[_]] { + def map[A, B](this x: F[A])(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 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] = + 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 = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = + ctx => x + } + + def mappAll[F[_]: Monad, T](x: T, fs: List[T => T]): F[T] = + fs.foldLeft(implicitly[Monad[F]].pure(x))((x: F[T], f: T => T) => + if (true) implicitly[Monad[F]].map(x)(f) + else if (true) x.map(f) + else x.map[T, T](f) + ) +} \ No newline at end of file From adf55ef84e62216cad2b11750ef8ddf4c86ab223 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 17 Oct 2018 17:56:27 +0200 Subject: [PATCH 03/55] Implement anonymous witnesses # Conflicts: # compiler/src/dotty/tools/dotc/ast/Desugar.scala --- .../src/dotty/tools/dotc/ast/Desugar.scala | 66 +++++++-- tests/run/witnesses-anonymous.check | 5 + tests/run/witnesses-anonymous.scala | 125 ++++++++++++++++++ 3 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 tests/run/witnesses-anonymous.check create mode 100644 tests/run/witnesses-anonymous.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index f17f2c7e0637..5003bf18a9ba 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -302,8 +302,8 @@ object desugar { /** The expansion of a class definition. See inline comments for what is involved */ def classDef(cdef: TypeDef)(implicit ctx: Context): Tree = { - val className = checkNotReservedName(cdef).asTypeName - val impl @ Template(_, _, self, _) = cdef.rhs + val impl @ Template(constr0, parents, self, _) = cdef.rhs + val className = normalizeClassName(cdef, impl) val parents = impl.parents val mods = cdef.mods val companionMods = mods @@ -722,9 +722,9 @@ object desugar { * final class name$ extends parents { self: name.type => body } */ def moduleDef(mdef: ModuleDef)(implicit ctx: Context): Tree = { - val moduleName = checkNotReservedName(mdef).asTermName val impl = mdef.impl val mods = mdef.mods + val moduleName = normalizeClassName(mdef, impl).toTermName def isEnumCase = mods.isEnumCase def flagSourcePos(flag: FlagSet) = mods.mods.find(_.flags == flag).fold(mdef.sourcePos)(_.sourcePos) @@ -802,19 +802,65 @@ object desugar { Thicket(aliasType :: companions.toList) } - /** The name of `mdef`, after checking that it does not redefine a Scala core class. - * If it does redefine, issue an error and return a mangled name instead of the original one. + /** The normalized name of `mdef`. This means + * 1. Check that the name does not redefine a Scala core class. + * If it does redefine, issue an error and return a mangled name instead of the original one. + * 2. If the name is missing (this can be the case for witnesses), invent one instead. */ - def checkNotReservedName(mdef: MemberDef)(implicit ctx: Context): Name = { - val name = mdef.name - if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name.toTypeName)) { + def normalizeClassName(mdef: MemberDef, impl: Template)(implicit ctx: Context): TypeName = { + var name = mdef.name.toTypeName + if (name.isEmpty) name = s"${inventName(impl)}_witness".toTypeName + if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name)) { def kind = if (name.isTypeName) "class" else "object" ctx.error(em"illegal redefinition of standard $kind $name", mdef.sourcePos) - name.errorName + name = name.errorName } - else name + name } + /** Invent a name for an anonymous witness with template `impl`. + */ + private def inventName(impl: Template)(implicit ctx: Context): String = + if (impl.parents.isEmpty) + impl.body.find { + case dd: DefDef if dd.mods.is(Extension) => true + case _ => false + } match { + case Some(DefDef(name, _, (vparam :: _) :: _, _, _)) => + s"${name}_of_${inventTypeName(vparam.tpt)}" + case _ => + ctx.error(i"anonymous witness must have `for` part or must define at least one extension method", impl.pos) + nme.ERROR.toString + } + else + impl.parents.map(inventTypeName(_)).mkString("_") + + private class NameExtractor(followArgs: Boolean) extends UntypedTreeAccumulator[String] { + private def extractArgs(args: List[Tree])(implicit ctx: Context): String = + args.map(argNameExtractor.apply("", _)).mkString("_") + override def apply(x: String, tree: Tree)(implicit ctx: Context): String = + if (x.isEmpty) + tree match { + case Select(pre, nme.CONSTRUCTOR) => foldOver(x, pre) + case tree: RefTree if tree.name.isTypeName => tree.name.toString + case tree: TypeDef => tree.name.toString + case tree: AppliedTypeTree if followArgs && tree.args.nonEmpty => + s"${apply(x, tree.tpt)}_${extractArgs(tree.args)}" + case tree: LambdaTypeTree => + apply(x, tree.body) + case tree: Tuple => + if (followArgs) extractArgs(tree.trees) else "Tuple" + case tree: Function if tree.args.nonEmpty => + if (followArgs) s"${extractArgs(tree.args)}_to_${apply("", tree.body)}" else "Function" + case _ => foldOver(x, tree) + } + else x + } + private val typeNameExtractor = new NameExtractor(followArgs = true) + private val argNameExtractor = new NameExtractor(followArgs = false) + + private def inventTypeName(tree: Tree)(implicit ctx: Context): String = typeNameExtractor("", tree) + /** val p1, ..., pN: T = E * ==> * makePatDef[[val p1: T1 = E]]; ...; makePatDef[[val pN: TN = E]] diff --git a/tests/run/witnesses-anonymous.check b/tests/run/witnesses-anonymous.check new file mode 100644 index 000000000000..a754cb78f072 --- /dev/null +++ b/tests/run/witnesses-anonymous.check @@ -0,0 +1,5 @@ +12.566370614359172 +hihelloworld +-2147483648 +3 +List(2) diff --git a/tests/run/witnesses-anonymous.scala b/tests/run/witnesses-anonymous.scala new file mode 100644 index 000000000000..782d96fa354c --- /dev/null +++ b/tests/run/witnesses-anonymous.scala @@ -0,0 +1,125 @@ +object Test extends App { + + implicit object O { + def em(this x: Int): Boolean = x > 0 + } + + assert(1.em == O.em(1)) + + case class Circle(x: Double, y: Double, radius: Double) + + witness { + def circumference(this c: Circle): Double = c.radius * math.Pi * 2 + } + + val circle = new Circle(1, 1, 2.0) + + println(circle.circumference) + + witness { + def longestStrings(this xs: Seq[String]): Seq[String] = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } + } + val names = List("hi", "hello", "world") + assert(names.longestStrings == List("hello", "world")) + + witness { + def second[T](this xs: Seq[T]) = xs.tail.head + } + + assert(names.longestStrings.second == "world") + + witness { + def flattened[T](this xs: List[List[T]]) = xs.foldLeft[List[T]](Nil)(_ ++ _) + } + + assert(List(names, List("!")).flattened == names :+ "!") + assert(Nil.flattened == Nil) + + trait SemiGroup[T] { + def combine(this x: T)(y: T): T + } + trait Monoid[T] extends SemiGroup[T] { + def unit: T + } + + // An instance declaration: + witness for Monoid[String] { + def combine(this x: String)(y: String): String = x.concat(y) + def unit: String = "" + } + + // Abstracting over a typeclass with a context bound: + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) + + println(sum(names)) + + 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 + val minimum: T + } + + witness for Ord[Int] { + def compareTo(this x: Int)(y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + val minimum = Int.MinValue + } + + witness [T: Ord] for Ord[List[T]] { + def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } + val minimum: List[T] = Nil + } + + def max[T: Ord](x: T, y: T): T = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) + + println(max(List[Int]())) + println(max(List(1, 2, 3))) + + println(max(List(1, 2, 3), List(2))) + + trait Functor[F[_]] { + def map[A, B](this x: F[A])(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 pure[A](x: A): F[A] + } + + witness for Monad[List] { + def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] = + xs.flatMap(f) + def pure[A](x: A): List[A] = + List(x) + } + + witness [Ctx] for Monad[[X] => Ctx => X] { + def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = + ctx => x + } + + def mappAll[F[_]: Monad, T](x: T, fs: List[T => T]): F[T] = + fs.foldLeft(implicitly[Monad[F]].pure(x))((x: F[T], f: T => T) => + if (true) implicitly[Monad[F]].map(x)(f) + else if (true) x.map(f) + else x.map[T, T](f) + ) +} \ No newline at end of file From 8539daa01ab644302380c74e8bfc92c390587ec4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 17 Oct 2018 18:00:55 +0200 Subject: [PATCH 04/55] Fix tests --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 10 ++++++++-- tests/run/t6111.scala | 2 +- tests/run/witnesses.check | 4 ++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index e16c098d7bdd..7da09816231d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2511,6 +2511,7 @@ object Parsers { newLineOptWhenFollowedBy(LBRACE) if ((name.isEmpty || parents.isEmpty) && in.token != LBRACE) syntaxErrorOrIncomplete(ExpectedTokenButFound(LBRACE, in.token)) + var mods1 = addMod(mods, witnessMod) val wdef = if (in.token == LBRACE) { val templ = templateBodyOpt(makeConstructor(tparams, vparamss), parents, isEnum = false) @@ -2524,9 +2525,14 @@ object Parsers { expr() } else EmptyTree - DefDef(name, tparams, vparamss, constrAppsToType(parents), rhs) + val tpt = constrAppsToType(parents) + if (tparams.isEmpty && vparamss.isEmpty) { + mods1 |= Lazy + ValDef(name, tpt, rhs) + } + else DefDef(name, tparams, vparamss, tpt, rhs) } - finalizeDef(wdef, addMod(mods, witnessMod), start) + finalizeDef(wdef, mods1, start) } /* -------- TEMPLATES ------------------------------------------- */ diff --git a/tests/run/t6111.scala b/tests/run/t6111.scala index 6f0164833794..915c23b446fe 100644 --- a/tests/run/t6111.scala +++ b/tests/run/t6111.scala @@ -4,7 +4,7 @@ // along with the real fix: an extractor pattern with 1 sub-pattern should type check for all extractors // that return Option[T], whatever T (even if it's a tuple) object Foo { - def unapply[S, T](scrutinee: S)(implicit witness: FooHasType[S, T]): Option[T] = scrutinee match { + def unapply[S, T](scrutinee: S)(implicit evidence: FooHasType[S, T]): Option[T] = scrutinee match { case i: Int => Some((i, i).asInstanceOf[T]) } } diff --git a/tests/run/witnesses.check b/tests/run/witnesses.check index e69de29bb2d1..5cd13ae2676a 100644 --- a/tests/run/witnesses.check +++ b/tests/run/witnesses.check @@ -0,0 +1,4 @@ +hihelloworld +-2147483648 +3 +List(2) From 2dc6c0879c414e8e2fc4052c464e338ae917e389 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Oct 2018 18:36:24 +0200 Subject: [PATCH 05/55] WIP Witness parameter syntax # Conflicts: # docs/docs/internals/syntax.md --- docs/docs/internals/syntax.md | 6 +- docs/docs/reference/witnesses.md | 334 +++++++++++++++++++++++++++++++ 2 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 docs/docs/reference/witnesses.md diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 07dd8887e662..573bb1b3511c 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -298,6 +298,8 @@ DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘ DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. +WitnessParams ::= WitnessParam {‘,’ WitnessParam} +WitnessParam ::= DefParam | ParamType ``` ### Bindings and Imports @@ -366,9 +368,9 @@ ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template) -WitnessDef ::= [id] [DefTypeParamClause] DefParamClauses +WitnessDef ::= [id] [DefTypeParamClause] [‘with’ WitnessParams] [‘for’ [ConstrApps]] TemplateBody - | id [DefTypeParamClause] DefParamClauses + | id [DefTypeParamClause] [‘with’ WitnessParams] [‘for’ Type] [‘=’ Expr] Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats) InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] diff --git a/docs/docs/reference/witnesses.md b/docs/docs/reference/witnesses.md new file mode 100644 index 000000000000..b2582755fdeb --- /dev/null +++ b/docs/docs/reference/witnesses.md @@ -0,0 +1,334 @@ +--- +layout: doc-page +title: "Witnesses" +--- + +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: + +```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 +} + +witness IntOrd for Ord[Int] { + def compareTo(this x: Int)(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 { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } +} +``` +Witness are shorthands for implicit definitions. The winesses above could also have been +formulated as implicits as follows: +```scala +implicit object IntOrd extends Ord[Int] { + def compareTo(this x: Int)(y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 +} + +class ListOrd[T: Ord] for Ord[List[T]] { + def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } +} +implicit def ListOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] +``` +In fact, a plausible compilation strategy would map the witnesses given above to exactly these implicit definitions. + +### Witness Parameters + +Witnesses can also have implicit value parameters. For instance, here is an alternative way to write the`ListOrd` witness: +```scala +witness ListOrd[T] with Ord[T] for Ord[List[T]] { ... } + +def max[T](xs: List[T]) with (Coercible[T, U]): T = ... + +``` + +### Motivation + +Given that witnesses are only a thin veneer on top of implicits, why introduce them? +There are several reasons. + + 1. Convey meaning instead of mechanism. Witnesses + + + +trait SemiGroup[T] { + def combine(this x: T)(y: T): T +} +trait Monoid[T] extends SemiGroup[T] { + def unit: T +} + +witness StringMonoid for Monoid[String] { + def combine(this x: String)(y: String): String = x.concat(y) + def unit: String = "" +} +``` + + + +Extension methods allow one to add methods to a type after the type is defined. Example: + +```scala +case class Circle(x: Double, y: Double, radius: Double) + +implicit object CircleOps { + def circumference(this c: Circle): Double = c.radius * math.Pi * 2 +} +``` + +`CircleOps` adds an extension method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: + +```scala + val circle = Circle(0, 0, 1) + circle.circumference +``` + +Extension methods are methods that have a `this` modifier for the first parameter. +They can also be invoked as plain methods. So the following holds: +```scala +assert(circle.circumference == CircleOps.circumference(circle)) +``` + + + +### Translation of Calls to Extension Methods + +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: + + +```scala +implicit object StringSeqOps1 { + def longestStrings(this xs: Seq[String]) = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} +``` +Then +```scala +List("here", "is", "a", "list").longestStrings +``` +is legal everywhere `StringSeqOps1` is available as an implicit value. Alternatively, we can define `longestStrings` +as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. + +```scala +object StringSeqOps2{ + def longestStrings(this xs: Seq[String]) = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} +import StringSeqOps2.longestStrings +List("here", "is", "a", "list").longestStrings +``` +The precise rules for resolving a selection to an extension method are as follows. + +Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, +and where `T` is the expected type. The following two rewritings are tried in order: + + 1. The selection is rewritten to `m[Ts](e)`. + 2. If the first rewriting does not typecheck with expected type `T`, and there is an implicit value `i` + in either the current scope or in the implicit scope of `T`, and `i` defines an extension + method named `m`, then selection is expanded to `i.m[Ts](e)`. + This second rewriting is attempted at the time where the compiler also tries an implicit conversion + from `T` to a type containing `m`. If there is more than one way of rewriting, an ambiguity error results. + +So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided +`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`). + +**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, +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: +```scala +def +: [T](this xs: Seq[T))(x: T): Seq[T] +``` + +### Generic Extensions + +The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible +to extend a generic type by adding type parameters to an extension method: + +```scala +implicit object ListOps { + def second[T](this xs: List[T]) = xs.tail.head +} +``` + +or: + + +```scala +implicit object ListListOps { + def flattened[T](this xs: List[List[T]]) = xs.foldLeft[List[T]](Nil)(_ ++ _) +} +``` + +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. + +### A Larger Example + +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. + +```scala +object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) + + implicit object Ensuring { + def ensuring[T](this x: T)(condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(x) + assert(condition) + x + } + } +} + +object Test { + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} +``` +**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean` +as the type of the condition of `ensuring`. An argument condition to `ensuring` such as +`(result == 6)` will therefore have an implicit value of type `WrappedResult[T]` in scope +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: + + { val result = List(1, 2, 3).sum + assert(result == 6) + result + } + +### Rules for Overriding Extension Methods + +Extension methods may override only extension methods and can be overridden only by extension methods. + +### Extension Methods and TypeClasses + +The rules for expanding extension methods make sure that they work seamlessly with typeclasses. For instance, consider `SemiGroup` and `Monoid`. +```scala + // Two typeclasses: + trait SemiGroup[T] { + def combine(this x: T)(y: T): T + } + trait Monoid[T] extends SemiGroup[T] { + def unit: T + } + + // An instance declaration: + implicit object StringMonoid extends Monoid[String] { + def combine(this x: String)(y: String): String = x.concat(y) + def unit: String = "" + } + + // Abstracting over a typeclass with a context bound: + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) +``` +In the last line, the call to `_.combine(_)` expands to `(x1, x2) => x1.combine(x2)`, +which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the implicit +evidence parameter summoned by the context bound `[T: Monoid]`. This works since +extension methods apply everywhere their enclosing object is available as an implicit. + +### Generic Extension Classes + +As another example, consider implementations of an `Ord` type class with a `minimum` value: +```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 + val minimum: T + } + + implicit object IntOrd extends Ord[Int] { + def compareTo(this x: Int)(y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + val minimum = Int.MinValue + } + + implicit class ListOrd[T: Ord] extends Ord[List[T]] { + def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } + val minimum: List[T] = Nil + } + + def max[T: Ord](x: T, y: T): T = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) +``` +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: +```scala + implicit def ListOrd[T: Ord]: ListOrd[T] = new ListOrd[T] +``` + +### Higher Kinds + +Extension methods generalize to higher-kinded types without requiring special provisions. Example: + +```scala + trait Functor[F[_]] { + def map[A, B](this x: F[A])(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 pure[A](x: A): F[A] + } + + implicit object ListMonad extends Monad[List] { + def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] = + xs.flatMap(f) + def pure[A](x: A): List[A] = + List(x) + } + + implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { + def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = + ctx => x + } +``` +### Syntax + +The required syntax extension just adds one clause for extension methods relative +to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). +``` +DefSig ::= id [DefTypeParamClause] [ExtParamClause] DefParamClauses +ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’ +``` + + + + From c2c843fef7d05765eb84179a12486071e8ab0932 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 25 Oct 2018 19:26:20 +0200 Subject: [PATCH 06/55] Complete reference documentation # Conflicts: # docs/docs/internals/syntax.md --- docs/docs/internals/syntax.md | 6 +- .../{witnesses.md => witnesses/motivation.md} | 39 +++- .../reference/witnesses/witness-params.md | 175 ++++++++++++++++++ docs/docs/reference/witnesses/witnesses.md | 170 +++++++++++++++++ docs/sidebar.yml | 7 + tests/pos/reference/witnesses.scala | 51 +++++ 6 files changed, 444 insertions(+), 4 deletions(-) rename docs/docs/reference/{witnesses.md => witnesses/motivation.md} (67%) create mode 100644 docs/docs/reference/witnesses/witness-params.md create mode 100644 docs/docs/reference/witnesses/witnesses.md create mode 100644 tests/pos/reference/witnesses.scala diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 573bb1b3511c..1274aa0f1cbc 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -136,7 +136,7 @@ ClassQualifier ::= ‘[’ id ‘]’ ### Types ```ebnf -Type ::= [FunArgMods] FunArgTypes ‘=>’ Type Function(ts, t) +Type ::= [FunArgMods | ‘.’] FunArgTypes ‘=>’ Type Function(ts, t) | HkTypeParamClause ‘=>’ Type TypeLambda(ps, t) | MatchType | InfixType @@ -175,7 +175,7 @@ TypeParamBounds ::= TypeBounds {‘<%’ Type} {‘:’ Type} ### Expressions ```ebnf -Expr ::= [FunArgMods] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) +Expr ::= [FunArgMods | ‘.’] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) | Expr1 BlockResult ::= [FunArgMods] FunParams ‘=>’ Block | Expr1 @@ -218,6 +218,7 @@ SimpleExpr1 ::= Literal | SimpleExpr ‘.’ id Select(expr, id) | SimpleExpr (TypeArgs | NamedTypeArgs) TypeApply(expr, args) | SimpleExpr1 ArgumentExprs Apply(expr, args) + | SimpleExpr1 ‘.’ ParArgumentExprs | XmlExpr ExprsInParens ::= ExprInParens {‘,’ ExprInParens} ExprInParens ::= PostfixExpr ‘:’ Type @@ -288,6 +289,7 @@ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [FunArgMods] ClsParams ‘)’] ClsParamClause ::= [nl] ‘(’ [ClsParams] ‘)’ + | ‘.’ ‘(’ ClsParams ‘)’ ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param diff --git a/docs/docs/reference/witnesses.md b/docs/docs/reference/witnesses/motivation.md similarity index 67% rename from docs/docs/reference/witnesses.md rename to docs/docs/reference/witnesses/motivation.md index b2582755fdeb..872a02b409ee 100644 --- a/docs/docs/reference/witnesses.md +++ b/docs/docs/reference/witnesses/motivation.md @@ -1,9 +1,44 @@ --- layout: doc-page -title: "Witnesses" +title: "Motivation" --- -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: +### Critique + +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. + +At the same time, implicits are also a controversal feature. I believe there are several reasons for this. + +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, +regarding the two definitions + + implicit def i1(implicit x: T): C[T] = ... + implicit def i2(x: T): C[T] = ... + +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. + +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. + +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. + +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 +```scala +def currentMap(implicit ctx: Context): Map[String, Int] +``` +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. + +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. + +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. + +### Alternative Design + +`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. + +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. The next sections elaborate +such an alternative design. + ```scala trait Ord[T] { diff --git a/docs/docs/reference/witnesses/witness-params.md b/docs/docs/reference/witnesses/witness-params.md new file mode 100644 index 000000000000..094c2ffe55f6 --- /dev/null +++ b/docs/docs/reference/witnesses/witness-params.md @@ -0,0 +1,175 @@ +--- +layout: doc-page +title: "Witness Parameters and Arguments" +--- + +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. + +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: +```scala +def maximum[T](xs: List[T]) + .(cmp: Ord[T]): T = + xs.reduceLeft((x, y) => if (x < y) y else x) + +def decending[T].(asc: Ord[T]): Ord[T] = new Ord[T] { + def compareTo(this x: Int)(y: Int) = asc.compareTo(y)(x) +} + +def minimum[T](xs: List[T]).(cmp: Ord[T]) = + maximum(xs).(descending) +``` +The example shows three methods that each have a witness parameter list for `Ord[T]`. +The `minimum` method's right hand side contains witness arguments `.(descending)`. +As is the case for implicit arguments, witness arguments can be left out. For instance, +given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:) +```scala +maximum(xs) +maximum(xs).(descending) +maximum(xs).(descending.(IntOrd)) +``` +Unlike for implicit parameters, witness arguments must be passed using the `.(...)` syntax. So the expression `maximum(xs)(descending)` would give a type error. + +Witness parameters translate straightforwardly to implicit parameters. Here are the previous three method definitions again, this time formulated using implicit parameters. +```scala +def maximum[T](xs: List[T]) + (implicit cmp: Ord[T]): T = + xs.reduceLeft((x, y) => if (x < y) y else x) + +def descending[T](implicit asc: Ord[T]): Ord[T] = new Ord[T] { + def compareTo(this x: T)(y: T) = asc.compareTo(y)(x) +} + +def minimum[T](xs: List[T])(implicit cmp: Ord[T]) = + maximum(xs)(descending) +``` + +## Anonymous Witness Parameters + +The ` :` part of a witness parameter can be left out. For instance, the `minimum` and `maximum` method definitions could be abbreviated to +```scala +def maximum[T](xs: List[T]).(_: Ord[T]): T = + xs.reduceLeft((x, y) => if (x < y) y else x) + +def minimum[T](xs: List[T]).(_: Ord[T]) = + maximum(xs).(descending) +``` + +## Summoning a Witness + +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]`. +The definition of `summon` is straightforward: +```scala +def summon[T].(x: T) = x +``` + +## Implicit Closures and Function Types + +A period ‘.’ in front of a parameter list also marks implicit closures and implicit function types. Examples for types: +```scala +.Context => T +.A => .B => T +.(A, B) => T +.(x: A, y: B) => T +``` +Examples for closures: +```scala +.ctx => ctx.value +.(ctx: Context) => ctx.value +.(a: A, b: B) => t +``` + +## Syntax + +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). +``` +TmplDef ::= ... + | ‘witness’ WitnessDef +WitnessDef ::= [id] WitnessClauses [‘for’ [ConstrApps]] TemplateBody + | [id] WitnessClauses [‘for’ Type] ‘=’ Expr + | id WitnessClauses ‘for’ Type +WitnessClauses ::= [DefTypeParamClause] [‘with’ DefParams] + +ClsParamClause ::= ... + | ‘.’ ‘(’ ClsParams ‘)’ +DefParamClause ::= ... + | ‘.’ ‘(’ DefParams ‘)’ +Type ::= ... + | ‘.’ FunArgTypes ‘=>’ Type +Expr ::= ... + | ‘.’ FunParams ‘=>’ Expr + +SimpleExpr1 ::= ... + | SimpleExpr1 ‘.’ ParArgumentExprs +``` + +## More Examples + +Semigroups and monoids: +```scala +trait SemiGroup[T] { + def combine(this x: T)(y: T): T +} +trait Monoid[T] extends SemiGroup[T] { + def unit: T +} + +witness for Monoid[String] { + def combine(this x: String)(y: String): String = x.concat(y) + def unit: String = "" +} + +def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) +``` +Functors and monads: +```scala +trait Functor[F[_]] { + def map[A, B](this x: F[A])(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 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] = + 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 = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = + ctx => x +} +``` +Implementing postconditions via `ensuring`: +```scala +object PostConditions { + opaque type WrappedResult[T] = T + + private witness WrappedResult { + def apply[T](x: T): WrappedResult[T] = x + def unwrap[T](this x: WrappedResult[T]): T = x + } + + def result[T].(wrapped: WrappedResult[T]): T = wrapped.unwrap + + witness { + def ensuring[T](this x: T)(condition: .WrappedResult[T] => Boolean): T = { + assert(condition.(WrappedResult(x))) + x + } + } +} + +object Test { + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} +``` diff --git a/docs/docs/reference/witnesses/witnesses.md b/docs/docs/reference/witnesses/witnesses.md new file mode 100644 index 000000000000..69396a2b2c7a --- /dev/null +++ b/docs/docs/reference/witnesses/witnesses.md @@ -0,0 +1,170 @@ +--- +layout: doc-page +title: "Witnesses" +--- + +Witnesses provide a concise and uniform syntax for defining implicit values. Example: + +```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 +} + +witness IntOrd for Ord[Int] { + def compareTo(this x: Int)(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 { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } +} +``` + +Witness can be seen as shorthands for implicit definitions. The winesses above could also have been formulated as implicits as follows: +```scala +implicit object IntOrd extends Ord[Int] { + def compareTo(this x: Int)(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 { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } +} +implicit def ListOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] +``` +In fact, a plausible compilation strategy would map the witnesses given above to exactly these implicit definitions. + +Implicit definitions are kept for the moment but should be be deprecated eventually. As we will see, the only kind of implicit definitions that canot be emulated by witnesses are implicit conversions. It's interesting that the `implicit` modifier would be relegated at some point to its original meaning of defining an implicit conversion (implicit parameters and definitions came later in Scala's evolution). + +Why prefer witnesses over implicit definitions? Their definitions are shorter, more uniform, and they focus on intent rather than mechanism: I.e. we define a _witness for_ a type, instead of an _implicit object_ that happens to _extend_ a type. Likewise, the `ListOrd` witness is shorter and clearer than the class/implicit def combo that emulates it. + +## Witnesses for Extension Methods + +Witnesses can also be defined without a `for` clause. A typical application is to use a witness to define some extension methods. Examples: + +```scala +witness StringOps { + def longestStrings(this xs: Seq[String]): Seq[String] = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} + +witness ListOps { + def second[T](this xs: List[T]) = xs.tail.head +} +``` +Witnesses like these translate directly to `implicit` objects. + +## Anonymous Witnesses + +The name of a witness definition can be left out. Examples: +```scala +witness for Ord[Int] { ... } +witness [T: Ord] for Ord[List[T]] { ... } + +witness { + def second[T](this xs: List[T]) = xs.tail.head +} +``` +If the name of a witness is missing, the compiler will synthesize a name from +the type in the for clause, or, if that is missing, from the first defined +extension method. Details remain to be specified. + +## Conditional Witnesses + +A witness can depend on another witness being defined. For instance: +```scala +trait Convertible[From, To] { + def convert (this x: From): To +} + +witness [From, To] with c: Convertible[From, To] for Convertible[List[From], List[To]] { + def convert (this x: ListFrom]): List[To] = x.map(c.convert) +} +``` + +The `with` clause in a witness defines required witnesses. The witness for `Convertible[List[From], List[To]]` above is defined only if a witness for `Convertible[From, To]` exists. +`with` clauses translate to implicit parameters if implicit defs. Here is the expansion of the anonmous witness above as an implicit def (the example demonstrates well the reduction +in boilerplate that witness syntax can achieve): +```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) +} +implicit def Convertible_List_List_witness[From, To](implicit c: Convertible[From, To]) + : Convertible[List[From], List[To]] = + new Convertible_List_List_witness[From, To] +``` +Context bounds in witness definitions also translate to implicit parameters, and therefore they can be represented alternatively as with clauses. For instance, here is an equivalent definition of the `ListOrd` witness: +```scala +witness ListOrd[T] with ord: Ord[T] for List[Ord[T]] { ... } +``` +An underscore ‘_’ can be used as the name of a required witness, if that witness does not +need to be referred to directly. For instance, the last `ListOrd` witness could also have been written like this: +```scala +witness ListOrd[T] with _: Ord[T] for List[Ord[T]] { ... } +``` + +## Abstract and Alias Witnesses + +Like implicit definitions, witnesses can be abstract. An abstract witness is characterized by not having a body after the `for` clause. Example: +```scala +trait TastyAPI { + type Symbol + trait SymDeco { + def name(this sym: Symbol): Name + def tpe(this sym: Symbol): Type + } + witness symDeco for SymDeco +} +``` +Abstract witnesses always have a `for` clause. They cannot be anonymous. + +Witnesses can also be defined as aliases of other values. Example: +```scala +witness symDeco for SymDeco = compilerSymOps +``` +As another example, if one had already defined classes `IntOrd` and `ListOrd`, witnesses for them could be defined as follows: +```scala +class IntOrd extends Ord[Int] { ... } +class ListOrd[T: Ord] extends Ord[List[T]] { ... } + +witness for Ord[Int] = new IntOrd +witness [T: Ord] for Ord[List[T]] = new ListOrd[T] +``` +The `for` clause in an alias witness is mandatory unless the witness definition is unconditional and occurs as a statement in a block. This corresponds to the same restriction for implicit vals in Scala 3. + +Abstract witnesses translate to abstract implicit methods. Alias witnesses translate to implicit defs if they are conditional or to implicit vals otherwise. For instance, the witnesses defined so far in this section translate to: +```scala +implicit def symDeco: SymDeco + +implicit val symDeco: SymDeco = compilerSymOps + +implicit val Ord_Int_witness: Ord[Int] = new IntOrd +implicit def Ord_List_witness(implicit ev: Ord[T]): Ord[List[T]] = new ListOrd[T] +``` +The `lazy` modifier is applicable to unconditional alias witnesses. If present, the translated implicit val is lazy. For instance, +```scala +lazy witness for Ord[Int] = new IntOrd +``` +would translate to +```scala +lazy implicit val Ord_Int_witness: Ord[Int] = new IntOrd +``` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 92e368b80b0c..15a74e968eb6 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -41,6 +41,13 @@ sidebar: url: docs/reference/enums/adts.html - title: Translation url: docs/reference/enums/desugarEnums.html + - title: Witnesses + - title: Motivation + url: docs/reference/witnesses/motivation.html + - title: Witness Definitions + url: docs/reference/witnesses/witnesses.html + - title: Witness Parameters + url: docs/reference/witnesses/witness-params.html - title: Other New Features subsection: - title: Typeclass Derivation diff --git a/tests/pos/reference/witnesses.scala b/tests/pos/reference/witnesses.scala new file mode 100644 index 000000000000..f7cf9a068a1e --- /dev/null +++ b/tests/pos/reference/witnesses.scala @@ -0,0 +1,51 @@ +class Common { + + 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 + } + + trait Convertible[From, To] { + def convert (this x: From): To + } + +} + +object Implicits extends Common { + implicit object IntOrd extends Ord[Int] { + def compareTo(this x: Int)(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 { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } + } + implicit def ListOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] + + 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) + } + implicit def Convertible_List_List_witness[From, To](implicit c: Convertible[From, To]) + : Convertible[List[From], List[To]] = + new Convertible_List_List_witness[From, To] + + def maximum[T](xs: List[T]) + (implicit cmp: Ord[T]): T = + xs.reduceLeft((x, y) => if (x < y) y else x) + + def descending[T](implicit asc: Ord[T]): Ord[T] = new Ord[T] { + def compareTo(this x: T)(y: T) = asc.compareTo(y)(x) + } + + def minimum[T](xs: List[T])(implicit cmp: Ord[T]) = + maximum(xs)(descending) +} \ No newline at end of file From ac28cc95e49bcc577ba93e12f46caa6212924a50 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Oct 2018 11:58:50 +0200 Subject: [PATCH 07/55] Some more details in docs --- docs/docs/reference/witnesses/motivation.md | 335 +----------------- .../reference/witnesses/witness-params.md | 42 ++- docs/docs/reference/witnesses/witnesses.md | 2 +- 3 files changed, 34 insertions(+), 345 deletions(-) diff --git a/docs/docs/reference/witnesses/motivation.md b/docs/docs/reference/witnesses/motivation.md index 872a02b409ee..9ce583f2285f 100644 --- a/docs/docs/reference/witnesses/motivation.md +++ b/docs/docs/reference/witnesses/motivation.md @@ -35,335 +35,10 @@ Can implicit function types help? Implicit function types allow to abstract over `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. -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. The next sections elaborate -such an alternative design. - - -```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 -} - -witness IntOrd for Ord[Int] { - def compareTo(this x: Int)(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 { - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = x.compareTo(y) - if (fst != 0) fst else xs1.compareTo(ys1) - } -} -``` -Witness are shorthands for implicit definitions. The winesses above could also have been -formulated as implicits as follows: -```scala -implicit object IntOrd extends Ord[Int] { - def compareTo(this x: Int)(y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 -} - -class ListOrd[T: Ord] for Ord[List[T]] { - def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match { - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = x.compareTo(y) - if (fst != 0) fst else xs1.compareTo(ys1) - } -} -implicit def ListOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] -``` -In fact, a plausible compilation strategy would map the witnesses given above to exactly these implicit definitions. - -### Witness Parameters - -Witnesses can also have implicit value parameters. For instance, here is an alternative way to write the`ListOrd` witness: -```scala -witness ListOrd[T] with Ord[T] for Ord[List[T]] { ... } - -def max[T](xs: List[T]) with (Coercible[T, U]): T = ... - -``` - -### Motivation - -Given that witnesses are only a thin veneer on top of implicits, why introduce them? -There are several reasons. - - 1. Convey meaning instead of mechanism. Witnesses - - - -trait SemiGroup[T] { - def combine(this x: T)(y: T): T -} -trait Monoid[T] extends SemiGroup[T] { - def unit: T -} - -witness StringMonoid for Monoid[String] { - def combine(this x: String)(y: String): String = x.concat(y) - def unit: String = "" -} -``` - - - -Extension methods allow one to add methods to a type after the type is defined. Example: - -```scala -case class Circle(x: Double, y: Double, radius: Double) - -implicit object CircleOps { - def circumference(this c: Circle): Double = c.radius * math.Pi * 2 -} -``` - -`CircleOps` adds an extension method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: - -```scala - val circle = Circle(0, 0, 1) - circle.circumference -``` - -Extension methods are methods that have a `this` modifier for the first parameter. -They can also be invoked as plain methods. So the following holds: -```scala -assert(circle.circumference == CircleOps.circumference(circle)) -``` - - - -### Translation of Calls to Extension Methods - -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: - - -```scala -implicit object StringSeqOps1 { - def longestStrings(this xs: Seq[String]) = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } -} -``` -Then -```scala -List("here", "is", "a", "list").longestStrings -``` -is legal everywhere `StringSeqOps1` is available as an implicit value. Alternatively, we can define `longestStrings` -as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. - -```scala -object StringSeqOps2{ - def longestStrings(this xs: Seq[String]) = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } -} -import StringSeqOps2.longestStrings -List("here", "is", "a", "list").longestStrings -``` -The precise rules for resolving a selection to an extension method are as follows. - -Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, -and where `T` is the expected type. The following two rewritings are tried in order: - - 1. The selection is rewritten to `m[Ts](e)`. - 2. If the first rewriting does not typecheck with expected type `T`, and there is an implicit value `i` - in either the current scope or in the implicit scope of `T`, and `i` defines an extension - method named `m`, then selection is expanded to `i.m[Ts](e)`. - This second rewriting is attempted at the time where the compiler also tries an implicit conversion - from `T` to a type containing `m`. If there is more than one way of rewriting, an ambiguity error results. - -So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided -`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`). - -**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, -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: -```scala -def +: [T](this xs: Seq[T))(x: T): Seq[T] -``` - -### Generic Extensions - -The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible -to extend a generic type by adding type parameters to an extension method: - -```scala -implicit object ListOps { - def second[T](this xs: List[T]) = xs.tail.head -} -``` - -or: - - -```scala -implicit object ListListOps { - def flattened[T](this xs: List[List[T]]) = xs.foldLeft[List[T]](Nil)(_ ++ _) -} -``` - -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. - -### A Larger Example - -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. - -```scala -object PostConditions { - opaque type WrappedResult[T] = T - - private object WrappedResult { - def wrap[T](x: T): WrappedResult[T] = x - def unwrap[T](x: WrappedResult[T]): T = x - } - - def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) - - implicit object Ensuring { - def ensuring[T](this x: T)(condition: implicit WrappedResult[T] => Boolean): T = { - implicit val wrapped = WrappedResult.wrap(x) - assert(condition) - x - } - } -} - -object Test { - import PostConditions._ - val s = List(1, 2, 3).sum.ensuring(result == 6) -} -``` -**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean` -as the type of the condition of `ensuring`. An argument condition to `ensuring` such as -`(result == 6)` will therefore have an implicit value of type `WrappedResult[T]` in scope -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: - - { val result = List(1, 2, 3).sum - assert(result == 6) - result - } - -### Rules for Overriding Extension Methods - -Extension methods may override only extension methods and can be overridden only by extension methods. - -### Extension Methods and TypeClasses - -The rules for expanding extension methods make sure that they work seamlessly with typeclasses. For instance, consider `SemiGroup` and `Monoid`. -```scala - // Two typeclasses: - trait SemiGroup[T] { - def combine(this x: T)(y: T): T - } - trait Monoid[T] extends SemiGroup[T] { - def unit: T - } - - // An instance declaration: - implicit object StringMonoid extends Monoid[String] { - def combine(this x: String)(y: String): String = x.concat(y) - def unit: String = "" - } - - // Abstracting over a typeclass with a context bound: - def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) -``` -In the last line, the call to `_.combine(_)` expands to `(x1, x2) => x1.combine(x2)`, -which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the implicit -evidence parameter summoned by the context bound `[T: Monoid]`. This works since -extension methods apply everywhere their enclosing object is available as an implicit. - -### Generic Extension Classes - -As another example, consider implementations of an `Ord` type class with a `minimum` value: -```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 - val minimum: T - } - - implicit object IntOrd extends Ord[Int] { - def compareTo(this x: Int)(y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 - val minimum = Int.MinValue - } - - implicit class ListOrd[T: Ord] extends Ord[List[T]] { - def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match { - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = x.compareTo(y) - if (fst != 0) fst else xs1.compareTo(ys1) - } - val minimum: List[T] = Nil - } - - def max[T: Ord](x: T, y: T): T = if (x < y) y else x - - def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) -``` -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: -```scala - implicit def ListOrd[T: Ord]: ListOrd[T] = new ListOrd[T] -``` - -### Higher Kinds - -Extension methods generalize to higher-kinded types without requiring special provisions. Example: - -```scala - trait Functor[F[_]] { - def map[A, B](this x: F[A])(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 pure[A](x: A): F[A] - } - - implicit object ListMonad extends Monad[List] { - def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] = - xs.flatMap(f) - def pure[A](x: A): List[A] = - List(x) - } - - implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { - def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B = - ctx => f(r(ctx))(ctx) - def pure[A](x: A): Ctx => A = - ctx => x - } -``` -### Syntax - -The required syntax extension just adds one clause for extension methods relative -to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). -``` -DefSig ::= id [DefTypeParamClause] [ExtParamClause] DefParamClauses -ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’ -``` - - +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. +The next sections elaborate such an alternative design. It consists of two proposals which are independent of each other: + - A proposal to replace implicit _definitions_ by [witness definitions](./witnesses.html) + . + - A proposal for a [new syntax](./witness-params.html) of implicit _parameters_ and their _arguments_. diff --git a/docs/docs/reference/witnesses/witness-params.md b/docs/docs/reference/witnesses/witness-params.md index 094c2ffe55f6..9cea286bed9c 100644 --- a/docs/docs/reference/witnesses/witness-params.md +++ b/docs/docs/reference/witnesses/witness-params.md @@ -3,7 +3,7 @@ layout: doc-page title: "Witness Parameters and Arguments" --- -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. +Witness parameters represent a new syntax for defining implicit parameters. Unlike traditional implicit parameters, witness parameters come with a matching syntax for applications that mirrors the parameter syntax. 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: ```scala @@ -27,7 +27,7 @@ maximum(xs) maximum(xs).(descending) maximum(xs).(descending.(IntOrd)) ``` -Unlike for implicit parameters, witness arguments must be passed using the `.(...)` syntax. So the expression `maximum(xs)(descending)` would give a type error. +Unlike for implicit parameters, witness arguments must be passed using the `.( )` syntax. So the expression `maximum(xs)(descending)` would give a type error. Witness parameters translate straightforwardly to implicit parameters. Here are the previous three method definitions again, this time formulated using implicit parameters. ```scala @@ -43,20 +43,9 @@ def minimum[T](xs: List[T])(implicit cmp: Ord[T]) = maximum(xs)(descending) ``` -## Anonymous Witness Parameters - -The ` :` part of a witness parameter can be left out. For instance, the `minimum` and `maximum` method definitions could be abbreviated to -```scala -def maximum[T](xs: List[T]).(_: Ord[T]): T = - xs.reduceLeft((x, y) => if (x < y) y else x) - -def minimum[T](xs: List[T]).(_: Ord[T]) = - maximum(xs).(descending) -``` - ## Summoning a Witness -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]`. +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]` does. The definition of `summon` is straightforward: ```scala def summon[T].(x: T) = x @@ -173,3 +162,28 @@ object Test { val s = List(1, 2, 3).sum.ensuring(result == 6) } ``` + +## Migration + +New and old syntax would co-exist initially. Rewrite rules could rewrite old synrax to new automatically. This is trivial in the case of implicit parameters and implicit function types. It is a bit more involved in the case of implicit definitions, since more extensive pattern matching is required to recognize a definition that can be rewritten to a witness. + +## Discussion + +Several alternatives to the proposed syntax for witness parameters were considered: + + - Leave `implicit` parameters as they are. This suffers from the problems stated + in the [motivation section](./motivation.md). + - Leave the syntax of `implicit` parameters but institute two changes: First, applications + of implicit patameters must be via the pseudo method `.explicitly(...)`. Second, there can be more than one implicit parameter list and implicit parameters may precede explicit ones. This fixes most of the discussed problems, but at the expense of a bulky explicit application syntax. Bulk can be a problem, for instance when the programmer tries to + construct an extensive explicit argument tree to figure out what went wrong with a missing + implicit. Another issue is that migration from old to new scheme would be tricky and + would likely take multiple language versions. + - Use a different syntactic marker than ‘.’ for designating implicit parameters. The ‘.’ is ideal for the application syntax, but might be a bit too inconspicuous for the parameter + syntax. Other ideas I played with were `?` and infix `with`. A problem with these + is that they have visually the precedence of an infix operator, but we really want something tighter than that. ‘.’ has the same precedence as normal application, so is ideal on the application side. Note also that the ‘.’ can be made to stand out more + through choice of formatting, e.g. like in the definition of `maximum` at the beginning of + this section. + + + + diff --git a/docs/docs/reference/witnesses/witnesses.md b/docs/docs/reference/witnesses/witnesses.md index 69396a2b2c7a..1cd878dc9cc4 100644 --- a/docs/docs/reference/witnesses/witnesses.md +++ b/docs/docs/reference/witnesses/witnesses.md @@ -1,6 +1,6 @@ --- layout: doc-page -title: "Witnesses" +title: "Witness Definitions" --- Witnesses provide a concise and uniform syntax for defining implicit values. Example: From ca24bc42a71e47378b5043b12fa6c2521a612b4f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 29 Oct 2018 09:31:47 +0100 Subject: [PATCH 08/55] Add witness as a modifier --- docs/docs/reference/witnesses/discussion.md | 64 ++++++ docs/docs/reference/witnesses/motivation.md | 16 +- .../reference/witnesses/witness-modifier.md | 58 ++++++ .../reference/witnesses/witness-params.md | 196 +++++++----------- docs/docs/reference/witnesses/witnesses.md | 91 ++++---- docs/sidebar.yml | 4 + 6 files changed, 265 insertions(+), 164 deletions(-) create mode 100644 docs/docs/reference/witnesses/discussion.md create mode 100644 docs/docs/reference/witnesses/witness-modifier.md diff --git a/docs/docs/reference/witnesses/discussion.md b/docs/docs/reference/witnesses/discussion.md new file mode 100644 index 000000000000..f1494e97f980 --- /dev/null +++ b/docs/docs/reference/witnesses/discussion.md @@ -0,0 +1,64 @@ +--- +layout: doc-page +title: "Discussion" +--- + +## Summary + +The witness proposal consists of three main parts: + + - Define a new [high-level syntax for witnesses](./witnesses.html) that works out better the intent underlying implicit definitions. + - Split the two meanings of implicit parameters into [separate concepts](./witness-params.html). One concept specifies that a parameter is implicitly applied, the other makes the parameter available as a witness. + - Allow `witness` as a [modifier](./witness-modifier.html) to replace remaining use cases for implicit definitions and to provide a lower level syntax into which witness definitions (with `witness` as a subject) can be translated. + +## Other Uses of `implicit` + +The only use cases that are not yet covered by the proposal are implicit classes and implicit conversions. We do not propose to use `witness` in place of `implicit` for these, since that would bring back the uncomfortable similarity between implicit conversions and parameterized implicit aliases. However, there is a way to drop implicit conversions entirely. Scala 3 already [defines](https://github.com/lampepfl/dotty/pull/2065) a class `ImplicitConverter` whose instances are available as implicit conversions. +```scala + abstract class ImplicitConverter[-T, +U] extends Function1[T, U] +``` +One could define all implicit conversions as witnesses of this class. E.g. +```scala +witness StringToToken for ImplicitConverter[String, Token] { + def apply(str: String): Token = new KeyWord(str) +} +``` +The fact that this syntax is more verbose than simple implicit defs could be a welcome side effect since it might dampen any over-enthusiasm for defining implicit conversions. + +That leaves implicit classes. Most use cases of implicit classes are probably already covered by extension methods. For the others, one could always fall back to a pair of a regular class and an `ImplicitConverter` witness. It would be good to do a survey to find out how many classes would be affected. + +## Migration + +New and old syntax would co-exist initially. Rewrite rules could rewrite old syntax to new automatically. This is trivial in the case of implicit parameters and implicit function types. It is a bit more involved in the case of implicit definitions, since more extensive pattern matching is required to recognize a definition that can be rewritten to a witness. + +## Discussion + +This is a rather sweeping proposal, which will affect most Scala code. Here are some supporting arguments and a summary of alternatives that were considered. + +The witness definition syntax makes the definition of implicit instances clearer and more concise. People have repeatedly asked for specific "typeclass syntax" in Scala. I believe that witnesses together with extension methods address this ask quite well. + +Probably the most far reaching and contentious changes affect implicit parameters. There might be resistance to change, because the existing scheme seems to work "well enough". However, I believe there is a price to pay for the status quo. The need to write `.apply` occasionally to force implicit arguments is already bad. Worse is the fact that implicits always have to come last, which makes useful program patterns much more cumbersome than before and makes the language less regular. Also problematic is the +lack of scope control for implicit parameters which caused the invention of macro-based libraries such as MacWire for what looks like an ideal task for implicits. Without the +changes proposed here, dependency injection in the style of MacWire will no longer be possible in Scala 3, since whitebox macros are going away. + +Several alternatives to the proposed syntax changes for implicit parameters were considered: + + 1. Leave `implicit` parameters as they are. This suffers from the problems stated + in the [motivation section](./motivation.md). + 2. Leave the syntax of `implicit` parameters but institute two changes: First, applications + of implicit parameters must be via the pseudo method `.explicitly(...)`. Second, there can be more than one implicit parameter list and implicit parameters may precede explicit ones. This fixes most of the discussed problems, but at the expense of a bulky explicit application syntax. Bulk can be a problem, for instance when the programmer tries to + construct an extensive explicit argument tree to figure out what went wrong with a missing + implicit. Another issue is that migration from old to new scheme would be tricky and + would likely take multiple language versions. + 3. The design presented here, but with `implicit` instead of `witness` as the modifier for + parameters. This is closer to the status quo, but migration presents problems: At what point will an `implicit` modifier stop having the old meaning and acquire the new one? Using `witness` instead of `implicit` solves that problem because old and new styles can coexist and it is always clear which is which. + 4. Don't split the meanings of implicitly passed parameters and witness parameters. Use prefix ‘.’ as a syntax + for both meanings together. This is more concise and relieves the programmer from needing to choose + which combination of functionality is desired. On the other hand, this does not support some + use patterns such as MacWire style dependency injection. Also, the prefix ‘.’ syntax is maybe + a bit too inconspicuous at the parameter definition site, even though it works very well at the + function application site. + 5. As in 4., but using `witness` or `implicit` as the syntax that marks an implicitly passed witness parameter. + This breaks the correspondence between function abstraction and application syntax. + +Once we have high-level witness definitions and witness parameters it's a small step to convert most the remaining uses of `implicit` to `witness` as a modifier. There are no hard technical reasons for doing so, but it looks more consistent with the other changes. diff --git a/docs/docs/reference/witnesses/motivation.md b/docs/docs/reference/witnesses/motivation.md index 9ce583f2285f..290e1187d6f8 100644 --- a/docs/docs/reference/witnesses/motivation.md +++ b/docs/docs/reference/witnesses/motivation.md @@ -7,9 +7,9 @@ title: "Motivation" 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. -At the same time, implicits are also a controversal feature. I believe there are several reasons for this. +At the same time, implicits are also a controversial feature. I believe there are several reasons for this. -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, +First, being very powerful, implicits are easily over-used and mis-used. This observation holds in almost all cases when we talk about _implicit conversions_, which, even though conceptually different, share the same syntax with other implicit definitions. For instance, regarding the two definitions implicit def i1(implicit x: T): C[T] = ... @@ -17,15 +17,17 @@ regarding the two definitions 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. -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. +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 deeply recursive implicit search has failed. The dotty compiler implements some improvements in this case, but further progress would be desirable. -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. +Third, the syntax of implicit definitions might be 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. 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 ```scala def currentMap(implicit ctx: Context): Map[String, Int] ``` -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. +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 whose type 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. + +Fifth, there is a lack of mechanism to contain the scope of implicits. Specifically, implicit parameters are always themselves implicit values in the body of the class or method in which they are defined. There are situations where one would like the parameter to a method or a class to be passed implicitly without that parameter becoming a candidate for further implicits in the body. The popular dependency injection framework MacWire exists precisely because such fine grained control of implicit scope is currently not available. 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. @@ -37,8 +39,10 @@ Can implicit function types help? Implicit function types allow to abstract over 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. -The next sections elaborate such an alternative design. It consists of two proposals which are independent of each other: +The next sections elaborate such an alternative design. It consists of three proposals which are independent of each other: - A proposal to replace implicit _definitions_ by [witness definitions](./witnesses.html) . - A proposal for a [new syntax](./witness-params.html) of implicit _parameters_ and their _arguments_. + + - A proposal to replace most or all remaining uses of the `implicit` modifier by a new `witness` [modifier](./witness-modifier.html). diff --git a/docs/docs/reference/witnesses/witness-modifier.md b/docs/docs/reference/witnesses/witness-modifier.md new file mode 100644 index 000000000000..e6e43222aebc --- /dev/null +++ b/docs/docs/reference/witnesses/witness-modifier.md @@ -0,0 +1,58 @@ +--- +layout: doc-page +title: "Witness as a Modifier" +--- + +There are some uses of implicits that cannot be expressed directly by witness definitions. In particular, we are lacking analogues of abstract implicit definitions and implicit aliases. The gap can be filled by allowing `witness` to be used as a modifier for objects, vals and defs, with the same meaning as the `implicit` modifier. Arguably, `witness` is a more suitable name for these kinds of definitions than `implicit`. + +## Abstract and Alias Witnesses + +As an example for an abstract witness consider the following fragment that's derived from Scala's Tasty extractor framework: +```scala +trait TastyAPI { + type Symbol + trait SymDeco { + def name(this sym: Symbol): Name + def tpe(this sym: Symbol): Type + } + witness def symDeco: SymDeco +} +``` +An example of a witness alias would be an implementation of `symDeco` in terms of some internal compiler structure: +```scala +trait TastyImpl extends TastyAPI { + witness val symDeco: SymDeco = compilerSymOps +} +``` +As is the case for `implicit` value definitions, the result type of a witness value is mandatory unless the definition occurs as a statement in a block. + +## Translation Scheme + +Witness-as-a-modifier can be seen as a more low-level and general syntax layer than witness definitions. The latter can be translated into the former. + +The translation rule for monomorphic unconditional witnesses is straightforward: +``` + witness for + --> + witness object extends +``` +The translation rule for parameterized or conditional witnesses is more involved: +``` + witness [] with for + --> + class [].( ) extends + witness def id [].( ): = + new [] + where + is a fresh, compiler generated name + is with every parameter prefixed by `witness` + is derived from by dropping all value parameters + is a list of type identifiers referring to each type parameter in +``` + +## Syntax + +``` +LocalModifier ::= ... + | witness +``` \ No newline at end of file diff --git a/docs/docs/reference/witnesses/witness-params.md b/docs/docs/reference/witnesses/witness-params.md index 9cea286bed9c..e5977c5808da 100644 --- a/docs/docs/reference/witnesses/witness-params.md +++ b/docs/docs/reference/witnesses/witness-params.md @@ -3,140 +3,105 @@ layout: doc-page title: "Witness Parameters and Arguments" --- -Witness parameters represent a new syntax for defining implicit parameters. Unlike traditional implicit parameters, witness parameters come with a matching syntax for applications that mirrors the parameter syntax. +This page presents new syntax for defining implicit parameters. Previously, implicit parameters, marked with `implicit`, were decoupled from implicit applications which looked like regular applications. On the other hand, external API and internal use were coupled - the `implicit` modifier specified both that missing arguments would be synthesized and that the parameter was in turn available as a candidate for implicit search. The new syntax reverts both of these decisions. Two different mechanisms control whether an argument to a parameter is synthesized and whether the parameter is available as a witness. Furthermore, explicit applications of methods with implicit parameters have a different syntax which matches the parameter definition syntax. -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: +The principal idea is to mark an implicit parameter or argument list with a preceding ‘.’. +If a parameter can itself be used as a witness in the body of a method or class, it is marked with `witness`. So the old syntax ```scala -def maximum[T](xs: List[T]) - .(cmp: Ord[T]): T = +def f(a: A)(implicit b: B, c: C) +``` +would now be expressed as +```scala +def f(a: A).(witness b: B, witness c: C) +``` +But the new syntax also allows for other possibilities: +```scala +def f1(a: A).(b: B, c: C) // b, c passed implicitly, but neither is available as a witness +def f2(a: A).(witness b: B, c: C) // b, c passed implicitly, only b available as a witness +def f3(a: A).(b: B, witness c: C) // b, c passed implicitly, only c available as a witness +def f4(witness a: A) // a passed explicitly, available as a witness +``` +To disambiguate between old and new syntax, we call implicit parameters under the new syntax "implicitly passed parameters", or, if they are marked with `witness`, "witness parameters" (in the rare case where a witness parameter is not implicitly passed, we will state that fact explicitly). The new implicit parameter syntax comes with a matching syntax for applications. If an argument list to a implicitly passed parameter is given, it also starts with a ‘.’. + +The following example shows shows three methods that each have a witness parameter list for `Ord[T]`. +```scala +def maximum[T](xs: List[T]).(witness cmp: Ord[T]): T = xs.reduceLeft((x, y) => if (x < y) y else x) -def decending[T].(asc: Ord[T]): Ord[T] = new Ord[T] { +def descending[T].(witness asc: Ord[T]): Ord[T] = new Ord[T] { def compareTo(this x: Int)(y: Int) = asc.compareTo(y)(x) } -def minimum[T](xs: List[T]).(cmp: Ord[T]) = +def minimum[T](xs: List[T]).(witness cmp: Ord[T]) = maximum(xs).(descending) ``` -The example shows three methods that each have a witness parameter list for `Ord[T]`. -The `minimum` method's right hand side contains witness arguments `.(descending)`. -As is the case for implicit arguments, witness arguments can be left out. For instance, +The `minimum` method's right hand side contains the explicit argument list `.(descending)`. +Explicit argument lists for implicitly passed parameters can be left out. For instance, given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:) ```scala maximum(xs) maximum(xs).(descending) maximum(xs).(descending.(IntOrd)) ``` -Unlike for implicit parameters, witness arguments must be passed using the `.( )` syntax. So the expression `maximum(xs)(descending)` would give a type error. +Unlike for traditional implicit parameters, arguments for implicitly passed parameters must be given using the `.( )` syntax. So the expression `maximum(xs)(descending)` would give a type error. -Witness parameters translate straightforwardly to implicit parameters. Here are the previous three method definitions again, this time formulated using implicit parameters. -```scala -def maximum[T](xs: List[T]) - (implicit cmp: Ord[T]): T = - xs.reduceLeft((x, y) => if (x < y) y else x) +## Application: Dependency Injection -def descending[T](implicit asc: Ord[T]): Ord[T] = new Ord[T] { - def compareTo(this x: T)(y: T) = asc.compareTo(y)(x) +Witnesses and implicitly passed parameters lend themselves well to dependency injection with constructor parameters. As an example, say we have four components `C1,...,C4` each of which depend on some subset of the other components. We can define these components as classes with implicitly passed parameters. E.g., +```scala +class C1.(c2: C2, c3: C3) { ... } +class C2.(c1: C1, c4: C4) { ... } +class C3.(c2: C3, c4: C4) { ... } +class C4.(c1: C1, c3: C3, c3: C3) { ... } +``` +The components can then be "wired together" by defining a set of local witnesses: +```scala +{ witness c1 for C1 + witness c2 for C2 + witness c3 for C3 + witness c4 for C4 + (c1, c2, c3, c4) } - -def minimum[T](xs: List[T])(implicit cmp: Ord[T]) = - maximum(xs)(descending) ``` +Note that component dependencies are _not_ defined themselves as witness parameters. This prevents components from spreading into the implicit namespace of other components and keeps the wiring strictly to the interface of these modules. + +This scheme is essentially what MacWire does. MacWire was implemented as a macro library. It requires whitebox macros which will no longer be supported in Scala 3. ## Summoning a Witness -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]` does. -The definition of `summon` is straightforward: +The `implicitly` method defined in `Predef` computes an implicit value for a given type. Keeping with the "witness" terminology, it seems apt to introduce the name `summon` for this operation. So `summon[T]` summons a witness for `T`, in the same way as `implicitly[T]` does. The definition of `summon` is straightforward: ```scala def summon[T].(x: T) = x ``` -## Implicit Closures and Function Types +## Implicit Function Types and Closures -A period ‘.’ in front of a parameter list also marks implicit closures and implicit function types. Examples for types: +Implicit function types are marked with period ‘.’ in front of a parameter list. Examples: ```scala .Context => T .A => .B => T .(A, B) => T .(x: A, y: B) => T ``` -Examples for closures: +Like methods, closures can also have parameters marked as `witness`. Examples: ```scala -.ctx => ctx.value -.(ctx: Context) => ctx.value -.(a: A, b: B) => t -``` - -## Syntax - -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). +case class Context(value: String) +witness ctx => ctx.value +(witness ctx: Context) => ctx.value +(a: A, witness b: B) => t ``` -TmplDef ::= ... - | ‘witness’ WitnessDef -WitnessDef ::= [id] WitnessClauses [‘for’ [ConstrApps]] TemplateBody - | [id] WitnessClauses [‘for’ Type] ‘=’ Expr - | id WitnessClauses ‘for’ Type -WitnessClauses ::= [DefTypeParamClause] [‘with’ DefParams] - -ClsParamClause ::= ... - | ‘.’ ‘(’ ClsParams ‘)’ -DefParamClause ::= ... - | ‘.’ ‘(’ DefParams ‘)’ -Type ::= ... - | ‘.’ FunArgTypes ‘=>’ Type -Expr ::= ... - | ‘.’ FunParams ‘=>’ Expr - -SimpleExpr1 ::= ... - | SimpleExpr1 ‘.’ ParArgumentExprs -``` - -## More Examples - -Semigroups and monoids: +Closures can also be marked with a prefix ‘.’. This makes the type of the closure +an implicit function type instead of a regular function type. As is the case for methods, a `witness` modifier for a closure parameter affects its internal use (by making the parameter available as a witness in the body) whereas ‘.’ affects the closure's external API and its type. To summarize: ```scala -trait SemiGroup[T] { - def combine(this x: T)(y: T): T -} -trait Monoid[T] extends SemiGroup[T] { - def unit: T -} - -witness for Monoid[String] { - def combine(this x: String)(y: String): String = x.concat(y) - def unit: String = "" -} - -def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) + (ctx: Context) => ctx.value : Context => String + (witness ctx: Context) => ctx.value : Context => String + .(ctx: Context) => ctx.value : .Context => String +.(witness ctx: Context) => ctx.value : .Context => String ``` -Functors and monads: -```scala -trait Functor[F[_]] { - def map[A, B](this x: F[A])(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) +Implicitly applied closures with prefix ‘.’ will probably be written only rarely. But the concept is needed as a way to explain the translation of implicit function types: If the expected type of a term _t_ is an implicit function type, _t_ will be turned into an implicitly applied closure of the form _.x => t_ unless _t_ is already such a closure. - def pure[A](x: A): F[A] -} +## Example -witness ListMonad for Monad[List] { - def flatMap[A, B](this xs: List[A])(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 = - ctx => f(r(ctx))(ctx) - def pure[A](x: A): Ctx => A = - ctx => x -} -``` Implementing postconditions via `ensuring`: ```scala object PostConditions { @@ -162,28 +127,21 @@ object Test { val s = List(1, 2, 3).sum.ensuring(result == 6) } ``` +## Syntax -## Migration - -New and old syntax would co-exist initially. Rewrite rules could rewrite old synrax to new automatically. This is trivial in the case of implicit parameters and implicit function types. It is a bit more involved in the case of implicit definitions, since more extensive pattern matching is required to recognize a definition that can be rewritten to a witness. - -## Discussion - -Several alternatives to the proposed syntax for witness parameters were considered: - - - Leave `implicit` parameters as they are. This suffers from the problems stated - in the [motivation section](./motivation.md). - - Leave the syntax of `implicit` parameters but institute two changes: First, applications - of implicit patameters must be via the pseudo method `.explicitly(...)`. Second, there can be more than one implicit parameter list and implicit parameters may precede explicit ones. This fixes most of the discussed problems, but at the expense of a bulky explicit application syntax. Bulk can be a problem, for instance when the programmer tries to - construct an extensive explicit argument tree to figure out what went wrong with a missing - implicit. Another issue is that migration from old to new scheme would be tricky and - would likely take multiple language versions. - - Use a different syntactic marker than ‘.’ for designating implicit parameters. The ‘.’ is ideal for the application syntax, but might be a bit too inconspicuous for the parameter - syntax. Other ideas I played with were `?` and infix `with`. A problem with these - is that they have visually the precedence of an infix operator, but we really want something tighter than that. ‘.’ has the same precedence as normal application, so is ideal on the application side. Note also that the ‘.’ can be made to stand out more - through choice of formatting, e.g. like in the definition of `maximum` at the beginning of - this section. - - - - +Here is the new syntax for parameters, arguments, and implicit function types seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). +``` +ClsParamClause ::= ... + | ‘.’ ‘(’ ClsParams ‘)’ +ClsParam ::= {Annotation} [{Modifier} (‘val’ | ‘var’) | ParamModifier] Param +DefParamClause ::= ... + | ‘.’ ‘(’ DefParams ‘)’ +DefParam ::= {Annotation} [ParamModifier] Param +ParamModifier ::= ‘inline’ | ‘witness’ +Type ::= ... + | ‘.’ FunArgTypes ‘=>’ Type +Expr ::= ... + | ‘.’ FunParams ‘=>’ Expr +SimpleExpr1 ::= ... + | SimpleExpr1 ‘.’ ParArgumentExprs +``` diff --git a/docs/docs/reference/witnesses/witnesses.md b/docs/docs/reference/witnesses/witnesses.md index 1cd878dc9cc4..09b2da8b96fe 100644 --- a/docs/docs/reference/witnesses/witnesses.md +++ b/docs/docs/reference/witnesses/witnesses.md @@ -29,7 +29,7 @@ witness ListOrd[T: Ord] for Ord[List[T]] { } ``` -Witness can be seen as shorthands for implicit definitions. The winesses above could also have been formulated as implicits as follows: +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) = @@ -50,9 +50,9 @@ implicit def ListOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] ``` In fact, a plausible compilation strategy would map the witnesses given above to exactly these implicit definitions. -Implicit definitions are kept for the moment but should be be deprecated eventually. As we will see, the only kind of implicit definitions that canot be emulated by witnesses are implicit conversions. It's interesting that the `implicit` modifier would be relegated at some point to its original meaning of defining an implicit conversion (implicit parameters and definitions came later in Scala's evolution). +Implicit definitions are kept for the moment but should be be deprecated eventually. As we will see, the only kind of implicit definitions that cannot be directly emulated by witnesses are implicit conversions. -Why prefer witnesses over implicit definitions? Their definitions are shorter, more uniform, and they focus on intent rather than mechanism: I.e. we define a _witness for_ a type, instead of an _implicit object_ that happens to _extend_ a type. Likewise, the `ListOrd` witness is shorter and clearer than the class/implicit def combo that emulates it. +Why prefer witnesses over implicit definitions? Their definitions are shorter, more uniform, and they focus on intent rather than mechanism: I.e. we define a _witness for_ a type, instead of an _implicit object_ that happens to _extend_ a type. Likewise, the `ListOrd` witness is shorter and clearer than the class/implicit def combo that emulates it. Arguably, `implicit` was always a misnomer. An `implicit object` is every inch as explicit as a plain object, it's just that the former is eligible as a synthesized argument to an implicit _parameter_. So, "implicit" makes sense as an adjective for parameters, but not so much for the other kinds of definitions. ## Witnesses for Extension Methods @@ -70,7 +70,7 @@ witness ListOps { def second[T](this xs: List[T]) = xs.tail.head } ``` -Witnesses like these translate directly to `implicit` objects. +Witnesses like these translate to `implicit` objects without an extends clause. ## Anonymous Witnesses @@ -101,8 +101,7 @@ witness [From, To] with c: Convertible[From, To] for Convertible[List[From], Lis ``` The `with` clause in a witness defines required witnesses. The witness for `Convertible[List[From], List[To]]` above is defined only if a witness for `Convertible[From, To]` exists. -`with` clauses translate to implicit parameters if implicit defs. Here is the expansion of the anonmous witness above as an implicit def (the example demonstrates well the reduction -in boilerplate that witness syntax can achieve): +`with` clauses translate to implicit parameters of implicit methods. Here is the expansion of the anonymous witness above in terms of a class and an implicit method (the example demonstrates well the reduction in boilerplate that witness syntax can achieve): ```scala class Convertible_List_List_witness[From, To](implicit c: Convertible[From, To]) extends Convertible[List[From], List[To]] { @@ -122,49 +121,63 @@ need to be referred to directly. For instance, the last `ListOrd` witness could witness ListOrd[T] with _: Ord[T] for List[Ord[T]] { ... } ``` -## Abstract and Alias Witnesses +## Witnesses as Typeclass Instances + +Here are some examples of witnesses for standard typeclasses: + +Semigroups and monoids: -Like implicit definitions, witnesses can be abstract. An abstract witness is characterized by not having a body after the `for` clause. Example: ```scala -trait TastyAPI { - type Symbol - trait SymDeco { - def name(this sym: Symbol): Name - def tpe(this sym: Symbol): Type - } - witness symDeco for SymDeco +trait SemiGroup[T] { + def combine(this x: T)(y: T): T +} +trait Monoid[T] extends SemiGroup[T] { + def unit: T } -``` -Abstract witnesses always have a `for` clause. They cannot be anonymous. -Witnesses can also be defined as aliases of other values. Example: -```scala -witness symDeco for SymDeco = compilerSymOps +witness for Monoid[String] { + def combine(this x: String)(y: String): String = x.concat(y) + def unit: String = "" +} + +def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) ``` -As another example, if one had already defined classes `IntOrd` and `ListOrd`, witnesses for them could be defined as follows: +Functors and monads: ```scala -class IntOrd extends Ord[Int] { ... } -class ListOrd[T: Ord] extends Ord[List[T]] { ... } +trait Functor[F[_]] { + def map[A, B](this x: F[A])(f: A => B): F[B] +} -witness for Ord[Int] = new IntOrd -witness [T: Ord] for Ord[List[T]] = new ListOrd[T] -``` -The `for` clause in an alias witness is mandatory unless the witness definition is unconditional and occurs as a statement in a block. This corresponds to the same restriction for implicit vals in Scala 3. +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) -Abstract witnesses translate to abstract implicit methods. Alias witnesses translate to implicit defs if they are conditional or to implicit vals otherwise. For instance, the witnesses defined so far in this section translate to: -```scala -implicit def symDeco: SymDeco + def pure[A](x: A): F[A] +} -implicit val symDeco: SymDeco = compilerSymOps +witness ListMonad for Monad[List] { + def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] = + xs.flatMap(f) + def pure[A](x: A): List[A] = + List(x) +} -implicit val Ord_Int_witness: Ord[Int] = new IntOrd -implicit def Ord_List_witness(implicit ev: Ord[T]): Ord[List[T]] = new ListOrd[T] +witness ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { + def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = + ctx => x +} ``` -The `lazy` modifier is applicable to unconditional alias witnesses. If present, the translated implicit val is lazy. For instance, -```scala -lazy witness for Ord[Int] = new IntOrd + +## Syntax + +Here is the new syntax for witness definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). ``` -would translate to -```scala -lazy implicit val Ord_Int_witness: Ord[Int] = new IntOrd +TmplDef ::= ... + | ‘witness’ WitnessDef +WitnessDef ::= [id] [DefTypeParamClause] [‘with’ DefParams] [‘for’ [ConstrApps] [TemplateBody] ``` +The identifier `id` can be omitted only if either the `for` part or the template body is present. If +the `for` part is missing, the template body must define at least one extension method. \ No newline at end of file diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 15a74e968eb6..dcc832bb9544 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -48,6 +48,10 @@ sidebar: url: docs/reference/witnesses/witnesses.html - title: Witness Parameters url: docs/reference/witnesses/witness-params.html + - title: Witness Modifier + url: docs/reference/witnesses/witness-modifier.html + - title: Discussion + url: docs/reference/witnesses/discussion.html - title: Other New Features subsection: - title: Typeclass Derivation From 6d97a2c567b2514601aecd326bde574ee5d28117 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 29 Oct 2018 09:31:47 +0100 Subject: [PATCH 09/55] Add witness as a modifier --- docs/docs/reference/witnesses/discussion.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/witnesses/discussion.md b/docs/docs/reference/witnesses/discussion.md index f1494e97f980..e243a4ab861a 100644 --- a/docs/docs/reference/witnesses/discussion.md +++ b/docs/docs/reference/witnesses/discussion.md @@ -53,7 +53,7 @@ Several alternatives to the proposed syntax changes for implicit parameters were 3. The design presented here, but with `implicit` instead of `witness` as the modifier for parameters. This is closer to the status quo, but migration presents problems: At what point will an `implicit` modifier stop having the old meaning and acquire the new one? Using `witness` instead of `implicit` solves that problem because old and new styles can coexist and it is always clear which is which. 4. Don't split the meanings of implicitly passed parameters and witness parameters. Use prefix ‘.’ as a syntax - for both meanings together. This is more concise and relieves the programmer from needing to choose + for both meanings together. This is more concise and relieves the programmer from having to choose which combination of functionality is desired. On the other hand, this does not support some use patterns such as MacWire style dependency injection. Also, the prefix ‘.’ syntax is maybe a bit too inconspicuous at the parameter definition site, even though it works very well at the @@ -61,4 +61,4 @@ Several alternatives to the proposed syntax changes for implicit parameters were 5. As in 4., but using `witness` or `implicit` as the syntax that marks an implicitly passed witness parameter. This breaks the correspondence between function abstraction and application syntax. -Once we have high-level witness definitions and witness parameters it's a small step to convert most the remaining uses of `implicit` to `witness` as a modifier. There are no hard technical reasons for doing so, but it looks more consistent with the other changes. +Once we have high-level witness definitions and witness parameters it's a small step to convert most the remaining uses of `implicit` to `witness` as a modifier. There are no stringent technical reasons for doing so, but it looks more consistent with the other changes. From 73cd252ca9c1779046881ac356079aac1461776d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 31 Oct 2018 10:52:05 +0100 Subject: [PATCH 10/55] Reworked proposal - drop distinction between implicitly passed and witness parameters - drop implicit as a modifier - use `with` instead of prefix `.` as connective for context parameters and arguments - use `|=>` for implicit function types and implicit closures # Conflicts: # docs/docs/internals/syntax.md --- docs/docs/internals/syntax.md | 23 ++-- docs/docs/reference/witnesses/discussion.md | 31 ++--- docs/docs/reference/witnesses/motivation.md | 13 +- .../reference/witnesses/witness-modifier.md | 58 -------- .../reference/witnesses/witness-params.md | 129 +++++++++--------- docs/docs/reference/witnesses/witnesses.md | 95 +++++++++++-- docs/sidebar.yml | 2 - 7 files changed, 174 insertions(+), 177 deletions(-) delete mode 100644 docs/docs/reference/witnesses/witness-modifier.md diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 1274aa0f1cbc..9f830793fc12 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -136,7 +136,8 @@ ClassQualifier ::= ‘[’ id ‘]’ ### Types ```ebnf -Type ::= [FunArgMods | ‘.’] FunArgTypes ‘=>’ Type Function(ts, t) +Type ::= [FunArgMods] FunArgTypes ‘=>’ Type Function(ts, t) + | FunArgMods ‘|=>’ Type | HkTypeParamClause ‘=>’ Type TypeLambda(ps, t) | MatchType | InfixType @@ -175,7 +176,8 @@ TypeParamBounds ::= TypeBounds {‘<%’ Type} {‘:’ Type} ### Expressions ```ebnf -Expr ::= [FunArgMods | ‘.’] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) +Expr ::= [FunArgMods] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) + | FunParams ‘|=>’ Expr | Expr1 BlockResult ::= [FunArgMods] FunParams ‘=>’ Block | Expr1 @@ -204,6 +206,7 @@ Catches ::= ‘catch’ Expr PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op) InfixExpr ::= PrefixExpr | InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr) + | InfixExpr ‘with’ (InfixExpr | ParArgumentExprs) PrefixExpr ::= [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr PrefixOp(expr, op) SimpleExpr ::= ‘new’ (ConstrApp [TemplateBody] | TemplateBody) New(constr | templ) | BlockExpr @@ -218,7 +221,6 @@ SimpleExpr1 ::= Literal | SimpleExpr ‘.’ id Select(expr, id) | SimpleExpr (TypeArgs | NamedTypeArgs) TypeApply(expr, args) | SimpleExpr1 ArgumentExprs Apply(expr, args) - | SimpleExpr1 ‘.’ ParArgumentExprs | XmlExpr ExprsInParens ::= ExprInParens {‘,’ ExprInParens} ExprInParens ::= PostfixExpr ‘:’ Type @@ -288,8 +290,7 @@ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | TypeBounds ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [FunArgMods] ClsParams ‘)’] -ClsParamClause ::= [nl] ‘(’ [ClsParams] ‘)’ - | ‘.’ ‘(’ ClsParams ‘)’ +ClsParamClause ::= [nl | ‘with’] ‘(’ [ClsParams] ‘)’ ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param @@ -297,11 +298,9 @@ Param ::= id ‘:’ ParamType [‘=’ Expr] | INT DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] -DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ +DefParamClause ::= [nl | ‘with’] ‘(’ [DefParams] ‘)’ DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. -WitnessParams ::= WitnessParam {‘,’ WitnessParam} -WitnessParam ::= DefParam | ParamType ``` ### Bindings and Imports @@ -370,10 +369,10 @@ ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template) -WitnessDef ::= [id] [DefTypeParamClause] [‘with’ WitnessParams] - [‘for’ [ConstrApps]] TemplateBody - | id [DefTypeParamClause] [‘with’ WitnessParams] - [‘for’ Type] [‘=’ Expr] +WitnessDef ::= [id] WitnessParams [‘for’ ConstrApps] [TemplateBody] + | id WitnessParams ‘:’ Type ‘=’ Expr + | id ‘=’ Expr +WitnessParams ::= [DefTypeParamClause] {‘with’ ‘(’ [DefParams] ‘)} Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats) InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] ConstrApps ::= ConstrApp {‘with’ ConstrApp} diff --git a/docs/docs/reference/witnesses/discussion.md b/docs/docs/reference/witnesses/discussion.md index e243a4ab861a..cbb17b267207 100644 --- a/docs/docs/reference/witnesses/discussion.md +++ b/docs/docs/reference/witnesses/discussion.md @@ -5,15 +5,14 @@ title: "Discussion" ## Summary -The witness proposal consists of three main parts: +The witness proposal consists of two main parts: - Define a new [high-level syntax for witnesses](./witnesses.html) that works out better the intent underlying implicit definitions. - - Split the two meanings of implicit parameters into [separate concepts](./witness-params.html). One concept specifies that a parameter is implicitly applied, the other makes the parameter available as a witness. - - Allow `witness` as a [modifier](./witness-modifier.html) to replace remaining use cases for implicit definitions and to provide a lower level syntax into which witness definitions (with `witness` as a subject) can be translated. + - Define a [new syntax for implicit parameters](./witness-params.html) that aligns formal parameters and arguments. ## Other Uses of `implicit` -The only use cases that are not yet covered by the proposal are implicit classes and implicit conversions. We do not propose to use `witness` in place of `implicit` for these, since that would bring back the uncomfortable similarity between implicit conversions and parameterized implicit aliases. However, there is a way to drop implicit conversions entirely. Scala 3 already [defines](https://github.com/lampepfl/dotty/pull/2065) a class `ImplicitConverter` whose instances are available as implicit conversions. +The only use cases that are not yet covered by the proposal are implicit conversions and implicit classes. We do not propose to use `witness` in place of `implicit` for these, since that would bring back the uncomfortable similarity between implicit conversions and parameterized implicit aliases. However, there is a way to drop implicit conversions entirely. Scala 3 already [defines](https://github.com/lampepfl/dotty/pull/2065) a class `ImplicitConverter` whose instances are available as implicit conversions. ```scala abstract class ImplicitConverter[-T, +U] extends Function1[T, U] ``` @@ -37,9 +36,10 @@ This is a rather sweeping proposal, which will affect most Scala code. Here are The witness definition syntax makes the definition of implicit instances clearer and more concise. People have repeatedly asked for specific "typeclass syntax" in Scala. I believe that witnesses together with extension methods address this ask quite well. -Probably the most far reaching and contentious changes affect implicit parameters. There might be resistance to change, because the existing scheme seems to work "well enough". However, I believe there is a price to pay for the status quo. The need to write `.apply` occasionally to force implicit arguments is already bad. Worse is the fact that implicits always have to come last, which makes useful program patterns much more cumbersome than before and makes the language less regular. Also problematic is the -lack of scope control for implicit parameters which caused the invention of macro-based libraries such as MacWire for what looks like an ideal task for implicits. Without the -changes proposed here, dependency injection in the style of MacWire will no longer be possible in Scala 3, since whitebox macros are going away. +A contentious point is whether we want abstract and alias witnesses. As an alternative, would could also keep the current syntax for implicit vals and defs, which can express the same concepts. The main advantage to introduce abstract and alias witnesses is that it would +allow us to drop implicit definitions altogether. + +Probably the most far reaching and contentious changes affect implicit parameters. There might be resistance to change, because the existing scheme seems to work "well enough". However, I believe there is a price to pay for the status quo. The need to write `.apply` occasionally to force implicit arguments is already bad. Worse is the fact that implicits always have to come last, which makes useful program patterns much more cumbersome than before and makes the language less regular. Several alternatives to the proposed syntax changes for implicit parameters were considered: @@ -50,15 +50,8 @@ Several alternatives to the proposed syntax changes for implicit parameters were construct an extensive explicit argument tree to figure out what went wrong with a missing implicit. Another issue is that migration from old to new scheme would be tricky and would likely take multiple language versions. - 3. The design presented here, but with `implicit` instead of `witness` as the modifier for - parameters. This is closer to the status quo, but migration presents problems: At what point will an `implicit` modifier stop having the old meaning and acquire the new one? Using `witness` instead of `implicit` solves that problem because old and new styles can coexist and it is always clear which is which. - 4. Don't split the meanings of implicitly passed parameters and witness parameters. Use prefix ‘.’ as a syntax - for both meanings together. This is more concise and relieves the programmer from having to choose - which combination of functionality is desired. On the other hand, this does not support some - use patterns such as MacWire style dependency injection. Also, the prefix ‘.’ syntax is maybe - a bit too inconspicuous at the parameter definition site, even though it works very well at the - function application site. - 5. As in 4., but using `witness` or `implicit` as the syntax that marks an implicitly passed witness parameter. - This breaks the correspondence between function abstraction and application syntax. - -Once we have high-level witness definitions and witness parameters it's a small step to convert most the remaining uses of `implicit` to `witness` as a modifier. There are no stringent technical reasons for doing so, but it looks more consistent with the other changes. + 3. Split the meanings of implicitly passed parameters and witness parameters. Use prefix ‘.’ as a syntax to indicate that an argument for a parameter can be passed explicitly. Use + `witness` as a parameter modifier to indicate that the parameter is available as a witness. + This scheme admits some new patterns, such as an explicit parameter that can be + used as a witness, or an implicitly passed parameter that is not a witness itself. + But the syntax looks unfamiliar and suffers from the choice paradox. diff --git a/docs/docs/reference/witnesses/motivation.md b/docs/docs/reference/witnesses/motivation.md index 290e1187d6f8..7b57ccc0c68b 100644 --- a/docs/docs/reference/witnesses/motivation.md +++ b/docs/docs/reference/witnesses/motivation.md @@ -25,9 +25,7 @@ Fourth, the syntax of implicit parameters has also some shortcomings. It starts ```scala def currentMap(implicit ctx: Context): Map[String, Int] ``` -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 whose type 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. - -Fifth, there is a lack of mechanism to contain the scope of implicits. Specifically, implicit parameters are always themselves implicit values in the body of the class or method in which they are defined. There are situations where one would like the parameter to a method or a class to be passed implicitly without that parameter becoming a candidate for further implicits in the body. The popular dependency injection framework MacWire exists precisely because such fine grained control of implicit scope is currently not available. +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 whose type depends on an implicit value. Finally, it's also a bit annoying that implicit parameters must have a name, even though in many cases that name is never referenced. 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. @@ -37,12 +35,9 @@ Can implicit function types help? Implicit function types allow to abstract over `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. -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. +I believe a good name for this concept is _witness_. A term is a witness for a type if it is an implicit instance of this type. It is 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. -The next sections elaborate such an alternative design. It consists of three proposals which are independent of each other: +The next sections elaborate such an alternative design. It consists of two proposals which are independent of each other: - - A proposal to replace implicit _definitions_ by [witness definitions](./witnesses.html) - . + - A proposal to replace implicit _definitions_ by [witness definitions](./witnesses.html). - A proposal for a [new syntax](./witness-params.html) of implicit _parameters_ and their _arguments_. - - - A proposal to replace most or all remaining uses of the `implicit` modifier by a new `witness` [modifier](./witness-modifier.html). diff --git a/docs/docs/reference/witnesses/witness-modifier.md b/docs/docs/reference/witnesses/witness-modifier.md deleted file mode 100644 index e6e43222aebc..000000000000 --- a/docs/docs/reference/witnesses/witness-modifier.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: doc-page -title: "Witness as a Modifier" ---- - -There are some uses of implicits that cannot be expressed directly by witness definitions. In particular, we are lacking analogues of abstract implicit definitions and implicit aliases. The gap can be filled by allowing `witness` to be used as a modifier for objects, vals and defs, with the same meaning as the `implicit` modifier. Arguably, `witness` is a more suitable name for these kinds of definitions than `implicit`. - -## Abstract and Alias Witnesses - -As an example for an abstract witness consider the following fragment that's derived from Scala's Tasty extractor framework: -```scala -trait TastyAPI { - type Symbol - trait SymDeco { - def name(this sym: Symbol): Name - def tpe(this sym: Symbol): Type - } - witness def symDeco: SymDeco -} -``` -An example of a witness alias would be an implementation of `symDeco` in terms of some internal compiler structure: -```scala -trait TastyImpl extends TastyAPI { - witness val symDeco: SymDeco = compilerSymOps -} -``` -As is the case for `implicit` value definitions, the result type of a witness value is mandatory unless the definition occurs as a statement in a block. - -## Translation Scheme - -Witness-as-a-modifier can be seen as a more low-level and general syntax layer than witness definitions. The latter can be translated into the former. - -The translation rule for monomorphic unconditional witnesses is straightforward: -``` - witness for - --> - witness object extends -``` -The translation rule for parameterized or conditional witnesses is more involved: -``` - witness [] with for - --> - class [].( ) extends - witness def id [].( ): = - new [] - where - is a fresh, compiler generated name - is with every parameter prefixed by `witness` - is derived from by dropping all value parameters - is a list of type identifiers referring to each type parameter in -``` - -## Syntax - -``` -LocalModifier ::= ... - | witness -``` \ No newline at end of file diff --git a/docs/docs/reference/witnesses/witness-params.md b/docs/docs/reference/witnesses/witness-params.md index e5977c5808da..3a17ad292cc2 100644 --- a/docs/docs/reference/witnesses/witness-params.md +++ b/docs/docs/reference/witnesses/witness-params.md @@ -1,104 +1,102 @@ --- layout: doc-page -title: "Witness Parameters and Arguments" +title: "Context Parameters and Arguments" --- -This page presents new syntax for defining implicit parameters. Previously, implicit parameters, marked with `implicit`, were decoupled from implicit applications which looked like regular applications. On the other hand, external API and internal use were coupled - the `implicit` modifier specified both that missing arguments would be synthesized and that the parameter was in turn available as a candidate for implicit search. The new syntax reverts both of these decisions. Two different mechanisms control whether an argument to a parameter is synthesized and whether the parameter is available as a witness. Furthermore, explicit applications of methods with implicit parameters have a different syntax which matches the parameter definition syntax. - -The principal idea is to mark an implicit parameter or argument list with a preceding ‘.’. -If a parameter can itself be used as a witness in the body of a method or class, it is marked with `witness`. So the old syntax -```scala -def f(a: A)(implicit b: B, c: C) -``` -would now be expressed as +This page presents new syntax for defining implicit parameters that aligns definition and call syntax. In both cases, the implicit parameter or argument now follows a `with` connective. +On the definition side, the old syntax ```scala -def f(a: A).(witness b: B, witness c: C) +def f(a: A)(implicit b: B) ``` -But the new syntax also allows for other possibilities: +is now expressed as ```scala -def f1(a: A).(b: B, c: C) // b, c passed implicitly, but neither is available as a witness -def f2(a: A).(witness b: B, c: C) // b, c passed implicitly, only b available as a witness -def f3(a: A).(b: B, witness c: C) // b, c passed implicitly, only c available as a witness -def f4(witness a: A) // a passed explicitly, available as a witness +def f(a: A) with (b: B) ``` -To disambiguate between old and new syntax, we call implicit parameters under the new syntax "implicitly passed parameters", or, if they are marked with `witness`, "witness parameters" (in the rare case where a witness parameter is not implicitly passed, we will state that fact explicitly). The new implicit parameter syntax comes with a matching syntax for applications. If an argument list to a implicitly passed parameter is given, it also starts with a ‘.’. +Implicit parameters defined with the new syntax are also called _context parameters_. +They come with a matching syntax for applications: explicit arguments for context parameters are also given after a `with`. -The following example shows shows three methods that each have a witness parameter list for `Ord[T]`. +The following example shows shows three methods that each have a context parameter for `Ord[T]`. ```scala -def maximum[T](xs: List[T]).(witness cmp: Ord[T]): T = +def maximum[T](xs: List[T]) with (cmp: Ord[T]): T = xs.reduceLeft((x, y) => if (x < y) y else x) -def descending[T].(witness asc: Ord[T]): Ord[T] = new Ord[T] { +def descending[T] with (asc: Ord[T]): Ord[T] = new Ord[T] { def compareTo(this x: Int)(y: Int) = asc.compareTo(y)(x) } -def minimum[T](xs: List[T]).(witness cmp: Ord[T]) = - maximum(xs).(descending) +def minimum[T](xs: List[T]) with (cmp: Ord[T]) = + maximum(xs) with descending ``` -The `minimum` method's right hand side contains the explicit argument list `.(descending)`. -Explicit argument lists for implicitly passed parameters can be left out. For instance, +The `minimum` method's right hand side defines the explicit argument `descending`. +Explicit arguments for context parameters can be left out. For instance, given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:) ```scala maximum(xs) -maximum(xs).(descending) -maximum(xs).(descending.(IntOrd)) +maximum(xs) with descending +maximum(xs) with (descending with IntOrd) +``` +Arguments for context parameters must be given using the `with` syntax. So the expression `maximum(xs)(descending)` would give a type error. + +The `with` connective is treated like an infix operator with the same precedence as other operators that start with a letter. The expression following a `with` may also be an argument list consisting of several implicit arguments separated by commas. If a tuple should be passed as a single implicit argument (probably an uncommon case), it has to be put in a pair of extra parentheses: +```scala +def f with (x: A, y: B) +f with (a, b) + +def g with (xy: (A, B)) +g with ((a, b)) ``` -Unlike for traditional implicit parameters, arguments for implicitly passed parameters must be given using the `.( )` syntax. So the expression `maximum(xs)(descending)` would give a type error. ## Application: Dependency Injection -Witnesses and implicitly passed parameters lend themselves well to dependency injection with constructor parameters. As an example, say we have four components `C1,...,C4` each of which depend on some subset of the other components. We can define these components as classes with implicitly passed parameters. E.g., +Witnesses can be used for dependency injection with constructor parameters. As an example, say we have four components `C1,...,C4` each of which depend on some subset of the other components. Constructor-based dependency injection defines these components as classes with explicitly passed parameters. E.g., ```scala -class C1.(c2: C2, c3: C3) { ... } -class C2.(c1: C1, c4: C4) { ... } -class C3.(c2: C3, c4: C4) { ... } -class C4.(c1: C1, c3: C3, c3: C3) { ... } +class C1(c2: C2, c3: C3) { ... } +class C2(c1: C1, c4: C4) { ... } +class C3(c2: C3, c4: C4) { ... } +class C4(c1: C1, c3: C3, c3: C3) { ... } ``` The components can then be "wired together" by defining a set of local witnesses: ```scala -{ witness c1 for C1 - witness c2 for C2 - witness c3 for C3 - witness c4 for C4 +{ witness c1 with (c1: C1, c2: C2, c3: C3) for C1(c1, c2, c3) + witness c2 with (c1: C1, c4: C4) for C2 (c1, c4) + witness c3 with (c2: C3, c4: C4) for C3(c2, c4) + witness c4 with (c1: C1, c3: C3, c4: C4) for C4(c1, c3, c4) (c1, c2, c3, c4) } ``` -Note that component dependencies are _not_ defined themselves as witness parameters. This prevents components from spreading into the implicit namespace of other components and keeps the wiring strictly to the interface of these modules. +Note that component dependencies in `C1, ..., C4` are _not_ defined themselves as implicit parameters. This prevents components from spreading into the implicit namespace of other components and keeps the wiring strictly to the interface of these modules. This scheme is essentially what MacWire does. MacWire was implemented as a macro library. It requires whitebox macros which will no longer be supported in Scala 3. +I considered for a while an alternative design where the two notions of an implicit parameter (argument gets synthesized vs. parameter is itself available as an implicit value) are separated. This would allow a nicer expression of component assembly which would not require that dependencies are repeated in the witnesses. The most significant downside of the alternative design is that it's likely to induce choice fatigue. In most cases, implicit parameters should be available itself as a witness, so asking for an opt-in each time a parameter is defined +became quickly tiresome. + ## Summoning a Witness The `implicitly` method defined in `Predef` computes an implicit value for a given type. Keeping with the "witness" terminology, it seems apt to introduce the name `summon` for this operation. So `summon[T]` summons a witness for `T`, in the same way as `implicitly[T]` does. The definition of `summon` is straightforward: ```scala -def summon[T].(x: T) = x +def summon[T] with (x: T) = x ``` ## Implicit Function Types and Closures -Implicit function types are marked with period ‘.’ in front of a parameter list. Examples: +Implicit function types are expressed using the new reserved operator `|=>`. Examples: ```scala -.Context => T -.A => .B => T -.(A, B) => T -.(x: A, y: B) => T +Context |=> T +A |=> B |=> T +(A, B) |=> T +(x: A, y: B) |=> T ``` -Like methods, closures can also have parameters marked as `witness`. Examples: +The `|=>` syntax was chosen for its resemblance with a turnstile symbol `|-` which signifies context dependencies. + +The `|=>` syntax can also be used for closures. It turns the parameter bindings into implicit +parameters. ```scala case class Context(value: String) -witness ctx => ctx.value -(witness ctx: Context) => ctx.value -(a: A, witness b: B) => t -``` -Closures can also be marked with a prefix ‘.’. This makes the type of the closure -an implicit function type instead of a regular function type. As is the case for methods, a `witness` modifier for a closure parameter affects its internal use (by making the parameter available as a witness in the body) whereas ‘.’ affects the closure's external API and its type. To summarize: -```scala - (ctx: Context) => ctx.value : Context => String - (witness ctx: Context) => ctx.value : Context => String - .(ctx: Context) => ctx.value : .Context => String -.(witness ctx: Context) => ctx.value : .Context => String +ctx |=> ctx.value +(ctx: Context) |=> ctx.value +(a: A, b: B) |=> t ``` -Implicitly applied closures with prefix ‘.’ will probably be written only rarely. But the concept is needed as a way to explain the translation of implicit function types: If the expected type of a term _t_ is an implicit function type, _t_ will be turned into an implicitly applied closure of the form _.x => t_ unless _t_ is already such a closure. ## Example @@ -112,11 +110,11 @@ object PostConditions { def unwrap[T](this x: WrappedResult[T]): T = x } - def result[T].(wrapped: WrappedResult[T]): T = wrapped.unwrap + def result[T] with (wrapped: WrappedResult[T]): T = wrapped.unwrap witness { - def ensuring[T](this x: T)(condition: .WrappedResult[T] => Boolean): T = { - assert(condition.(WrappedResult(x))) + def ensuring[T](this x: T)(condition: WrappedResult[T] |=> Boolean): T = { + assert(condition with WrappedResult(x)) x } } @@ -132,16 +130,13 @@ object Test { Here is the new syntax for parameters, arguments, and implicit function types seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). ``` ClsParamClause ::= ... - | ‘.’ ‘(’ ClsParams ‘)’ -ClsParam ::= {Annotation} [{Modifier} (‘val’ | ‘var’) | ParamModifier] Param + | ‘with’ ‘(’ [ClsParams] ‘)’ DefParamClause ::= ... - | ‘.’ ‘(’ DefParams ‘)’ -DefParam ::= {Annotation} [ParamModifier] Param -ParamModifier ::= ‘inline’ | ‘witness’ + | ‘with’ ‘(’ [DefParams] ‘)’ Type ::= ... - | ‘.’ FunArgTypes ‘=>’ Type + | FunArgTypes ‘|=>’ Type Expr ::= ... - | ‘.’ FunParams ‘=>’ Expr -SimpleExpr1 ::= ... - | SimpleExpr1 ‘.’ ParArgumentExprs + | FunParams ‘|=>’ Expr +InfixExpr ::= ... + | InfixExpr ‘with’ (InfixExpr | ParArgumentExprs) ``` diff --git a/docs/docs/reference/witnesses/witnesses.md b/docs/docs/reference/witnesses/witnesses.md index 09b2da8b96fe..e876e77f88f9 100644 --- a/docs/docs/reference/witnesses/witnesses.md +++ b/docs/docs/reference/witnesses/witnesses.md @@ -52,11 +52,11 @@ In fact, a plausible compilation strategy would map the witnesses given above to Implicit definitions are kept for the moment but should be be deprecated eventually. As we will see, the only kind of implicit definitions that cannot be directly emulated by witnesses are implicit conversions. -Why prefer witnesses over implicit definitions? Their definitions are shorter, more uniform, and they focus on intent rather than mechanism: I.e. we define a _witness for_ a type, instead of an _implicit object_ that happens to _extend_ a type. Likewise, the `ListOrd` witness is shorter and clearer than the class/implicit def combo that emulates it. Arguably, `implicit` was always a misnomer. An `implicit object` is every inch as explicit as a plain object, it's just that the former is eligible as a synthesized argument to an implicit _parameter_. So, "implicit" makes sense as an adjective for parameters, but not so much for the other kinds of definitions. +Why prefer witnesses over implicit definitions? Their definitions are shorter, more uniform, and they focus on intent rather than mechanism: I.e. we define a _witness for_ a type, instead of an _implicit object_ that happens to _extend_ a type. Likewise, the `ListOrd` witness is shorter and clearer than the class/implicit def combo that emulates it. Arguably, `implicit` was always a misnomer. An `implicit object` is every bit as explicit as a plain object, it's just that the former is eligible as a synthesized argument to an implicit _parameter_. So, "implicit" makes sense as an adjective for parameters, but not so much for the other kinds of definitions. ## Witnesses for Extension Methods -Witnesses can also be defined without a `for` clause. A typical application is to use a witness to define some extension methods. Examples: +Witnesses can also be defined without a `for` clause. A typical application is to use a witness to define extension methods. Examples: ```scala witness StringOps { @@ -95,7 +95,7 @@ trait Convertible[From, To] { def convert (this x: From): To } -witness [From, To] with c: Convertible[From, To] for Convertible[List[From], List[To]] { +witness [From, To] with (c: Convertible[From, To]) for Convertible[List[From], List[To]] { def convert (this x: ListFrom]): List[To] = x.map(c.convert) } ``` @@ -113,12 +113,85 @@ implicit def Convertible_List_List_witness[From, To](implicit c: Convertible[Fro ``` Context bounds in witness definitions also translate to implicit parameters, and therefore they can be represented alternatively as with clauses. For instance, here is an equivalent definition of the `ListOrd` witness: ```scala -witness ListOrd[T] with ord: Ord[T] for List[Ord[T]] { ... } +witness ListOrd[T] with (ord: Ord[T]) for List[Ord[T]] { ... } ``` An underscore ‘_’ can be used as the name of a required witness, if that witness does not need to be referred to directly. For instance, the last `ListOrd` witness could also have been written like this: ```scala -witness ListOrd[T] with _: Ord[T] for List[Ord[T]] { ... } +witness ListOrd[T] with (_: Ord[T]) for List[Ord[T]] { ... } +``` + +**Design note:** An alternative to the underscore syntax would be to allow the `name:` part to be left out entirely. I.e. it would then be `witness ListOrd[T] with (Ord[T]) for ...`. I am not yet sure which is preferable. + +## Abstract and Alias Witnesses + +Witness definitions can be abstract. +As an example for an abstract witness consider the following fragment that's derived from Scala's Tasty extractor framework: +```scala +trait TastyAPI { + type Symbol + trait SymDeco { + def name(this sym: Symbol): Name + def tpe(this sym: Symbol): Type + } + witness symDeco: SymDeco +} +``` +Here, `symDeco` is available as a witness for the `SymDeco` trait but its actual implementation +is deferred to subclasses of the `TastyAPI` trait. + +An example of an alias witness would be an implementation of `symDeco` in terms of some internal compiler structure: +```scala +trait TastyImpl extends TastyAPI { + witness symDeco: SymDeco = compilerSymOps +} +``` +Note that the result type of an abstract or alias witness is introduced with a colon instead of a `for`. This seems more natural since it evokes the similarity to implicit parameters, whose type is also given following a `:`. It also avoids the syntactic ambiguity with a witness +for a class that does not add any new definitions. I.e. +```scala +witness a for C // concrete witness for class C, no definitions added +witness b: C // abstract witness for class C +``` +Further examples of alias witnesses: +```scala +witness ctx = outer.ctx +witness ctx: 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) +``` +As another example, if one had already defined classes `IntOrd` and `ListOrd`, witnesses for them could be defined as follows: +```scala +class IntOrd extends Ord[Int] { ... } +class ListOrd[T: Ord] extends Ord[List[T]] { ... } + +witness intOrd: Ord[Int] = new IntOrd +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 translate to abstract implicit defs. Alias witnesses translate to implicit defs if they are parameterized or to implicit vals otherwise. For instance, the witnesses defined so far in this section translate 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 f[T]: C[T] = new C[T] +implicit def g(implicit ctx: Context): D = new D(ctx) + +implicit val intOrd: Ord[Int] = new IntOrd +implicit def listOrd(implicit ev: Ord[T]): Ord[List[T]] = new ListOrd[T] +``` +The `lazy` modifier is applicable to unparameterized alias witnesses. If present, the resulting implicit val is lazy. For instance, +```scala +lazy witness intOrd2: Ord[Int] = new IntOrd +``` +would translate to +```scala +lazy implicit val intOrd2: Ord[Int] = new IntOrd ``` ## Witnesses as Typeclass Instances @@ -175,9 +248,11 @@ witness ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { Here is the new syntax for witness definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). ``` -TmplDef ::= ... - | ‘witness’ WitnessDef -WitnessDef ::= [id] [DefTypeParamClause] [‘with’ DefParams] [‘for’ [ConstrApps] [TemplateBody] +TmplDef ::= ... + | ‘witness’ WitnessDef +WitnessDef ::= [id] WitnessParams [‘for’ ConstrApps] [TemplateBody] + | id WitnessParams ‘:’ Type ‘=’ Expr + | id ‘=’ Expr +WitnessParams ::= [DefTypeParamClause] {‘with’ ‘(’ [DefParams] ‘)} ``` -The identifier `id` can be omitted only if either the `for` part or the template body is present. If -the `for` part is missing, the template body must define at least one extension method. \ No newline at end of file +The identifier `id` can be omitted only if either the `for` part or the template body is present. If the `for` part is missing, the template body must define at least one extension method. \ No newline at end of file diff --git a/docs/sidebar.yml b/docs/sidebar.yml index dcc832bb9544..69217a0a816d 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -48,8 +48,6 @@ sidebar: url: docs/reference/witnesses/witnesses.html - title: Witness Parameters url: docs/reference/witnesses/witness-params.html - - title: Witness Modifier - url: docs/reference/witnesses/witness-modifier.html - title: Discussion url: docs/reference/witnesses/discussion.html - title: Other New Features From ae3f61303b0f716dbb06f4d06d60564c75c19cc7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 31 Oct 2018 11:46:57 +0100 Subject: [PATCH 11/55] Split out abstract and alias witnesses into separate proposal --- docs/docs/reference/witnesses/discussion.md | 25 ++-- docs/docs/reference/witnesses/motivation.md | 5 +- .../witnesses/replacing-implicits.md | 112 ++++++++++++++++++ .../reference/witnesses/witness-params.md | 7 -- docs/docs/reference/witnesses/witnesses.md | 72 ----------- docs/sidebar.yml | 2 + 6 files changed, 125 insertions(+), 98 deletions(-) create mode 100644 docs/docs/reference/witnesses/replacing-implicits.md diff --git a/docs/docs/reference/witnesses/discussion.md b/docs/docs/reference/witnesses/discussion.md index cbb17b267207..f901d5e48de8 100644 --- a/docs/docs/reference/witnesses/discussion.md +++ b/docs/docs/reference/witnesses/discussion.md @@ -9,36 +9,22 @@ The witness proposal consists of two main parts: - Define a new [high-level syntax for witnesses](./witnesses.html) that works out better the intent underlying implicit definitions. - Define a [new syntax for implicit parameters](./witness-params.html) that aligns formal parameters and arguments. + - Define [abstract and alias witnesses](./replacing-implicits.html) and replace all existing usages of `implicit` in the language. -## Other Uses of `implicit` - -The only use cases that are not yet covered by the proposal are implicit conversions and implicit classes. We do not propose to use `witness` in place of `implicit` for these, since that would bring back the uncomfortable similarity between implicit conversions and parameterized implicit aliases. However, there is a way to drop implicit conversions entirely. Scala 3 already [defines](https://github.com/lampepfl/dotty/pull/2065) a class `ImplicitConverter` whose instances are available as implicit conversions. -```scala - abstract class ImplicitConverter[-T, +U] extends Function1[T, U] -``` -One could define all implicit conversions as witnesses of this class. E.g. -```scala -witness StringToToken for ImplicitConverter[String, Token] { - def apply(str: String): Token = new KeyWord(str) -} -``` -The fact that this syntax is more verbose than simple implicit defs could be a welcome side effect since it might dampen any over-enthusiasm for defining implicit conversions. - -That leaves implicit classes. Most use cases of implicit classes are probably already covered by extension methods. For the others, one could always fall back to a pair of a regular class and an `ImplicitConverter` witness. It would be good to do a survey to find out how many classes would be affected. ## Migration New and old syntax would co-exist initially. Rewrite rules could rewrite old syntax to new automatically. This is trivial in the case of implicit parameters and implicit function types. It is a bit more involved in the case of implicit definitions, since more extensive pattern matching is required to recognize a definition that can be rewritten to a witness. +The third part (replacing existing implicits) should be adopted well after the first two parts +are implemented. Alias and abstract witnesses could be introduced together with the other witness definitions, but could also come later. + ## Discussion This is a rather sweeping proposal, which will affect most Scala code. Here are some supporting arguments and a summary of alternatives that were considered. The witness definition syntax makes the definition of implicit instances clearer and more concise. People have repeatedly asked for specific "typeclass syntax" in Scala. I believe that witnesses together with extension methods address this ask quite well. -A contentious point is whether we want abstract and alias witnesses. As an alternative, would could also keep the current syntax for implicit vals and defs, which can express the same concepts. The main advantage to introduce abstract and alias witnesses is that it would -allow us to drop implicit definitions altogether. - Probably the most far reaching and contentious changes affect implicit parameters. There might be resistance to change, because the existing scheme seems to work "well enough". However, I believe there is a price to pay for the status quo. The need to write `.apply` occasionally to force implicit arguments is already bad. Worse is the fact that implicits always have to come last, which makes useful program patterns much more cumbersome than before and makes the language less regular. Several alternatives to the proposed syntax changes for implicit parameters were considered: @@ -55,3 +41,6 @@ Several alternatives to the proposed syntax changes for implicit parameters were This scheme admits some new patterns, such as an explicit parameter that can be used as a witness, or an implicitly passed parameter that is not a witness itself. But the syntax looks unfamiliar and suffers from the choice paradox. + +A contentious point is whether we want abstract and alias witnesses. As an alternative, one would could also keep the current syntax for implicit vals and defs, which can express the same concepts. The main advantage to introduce abstract and alias witnesses is that it would +allow us to drop implicit definitions altogether. \ No newline at end of file diff --git a/docs/docs/reference/witnesses/motivation.md b/docs/docs/reference/witnesses/motivation.md index 7b57ccc0c68b..31b1b8eca6f3 100644 --- a/docs/docs/reference/witnesses/motivation.md +++ b/docs/docs/reference/witnesses/motivation.md @@ -37,7 +37,10 @@ Can implicit function types help? Implicit function types allow to abstract over I believe a good name for this concept is _witness_. A term is a witness for a type if it is an implicit instance of this type. It is 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. -The next sections elaborate such an alternative design. It consists of two proposals which are independent of each other: +The next sections elaborate such an alternative design. It consists of three proposals: - A proposal to replace implicit _definitions_ by [witness definitions](./witnesses.html). - A proposal for a [new syntax](./witness-params.html) of implicit _parameters_ and their _arguments_. + - A proposal to [replace all remaining usages](./replacing-implicits) of `implicit` in the language. + +The first two proposals are independent of each other. The last one would work only if the first two are adopted. diff --git a/docs/docs/reference/witnesses/replacing-implicits.md b/docs/docs/reference/witnesses/replacing-implicits.md new file mode 100644 index 000000000000..0db8548d3fea --- /dev/null +++ b/docs/docs/reference/witnesses/replacing-implicits.md @@ -0,0 +1,112 @@ +--- +layout: doc-page +title: "Replacing Implicits" +--- + +The previous two pages proposed high-level syntax for implicit definitions and a new syntax for implicit parameters. + +This addresses all the issues mentioned in the [Motivation](./motivation.md), but it leaves us with two related constructs: new style witnesses and context parameters and traditional implicits. This page discusses what would be needed to get rid of `implicit` entirely. + +## Abstract and Alias Witnesses + +Witness definitions can be abstract. +As an example for an abstract witness consider the following fragment that's derived from Scala's Tasty extractor framework: +```scala +trait TastyAPI { + type Symbol + trait SymDeco { + def name(this sym: Symbol): Name + def tpe(this sym: Symbol): Type + } + witness symDeco: SymDeco +} +``` +Here, `symDeco` is available as a witness for the `SymDeco` trait but its actual implementation +is deferred to subclasses of the `TastyAPI` trait. + +An example of an alias witness would be an implementation of `symDeco` in terms of some internal compiler structure: +```scala +trait TastyImpl extends TastyAPI { + witness symDeco: SymDeco = compilerSymOps +} +``` +Note that the result type of an abstract or alias witness is introduced with a colon instead of a `for`. This seems more natural since it evokes the similarity to implicit parameters, whose type is also given following a `:`. It also avoids the syntactic ambiguity with a witness +for a class that does not add any new definitions. I.e. +```scala +witness a for C // concrete witness for class C, no definitions added +witness b: C // abstract witness for class C +``` +Further examples of alias witnesses: +```scala +witness ctx = outer.ctx +witness ctx: 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) +``` +As another example, if one had already defined classes `IntOrd` and `ListOrd`, witnesses for them could be defined as follows: +```scala +class IntOrd extends Ord[Int] { ... } +class ListOrd[T: Ord] extends Ord[List[T]] { ... } + +witness intOrd: Ord[Int] = new IntOrd +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: +```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 f[T]: C[T] = new C[T] +implicit def g(implicit ctx: Context): D = new D(ctx) + +implicit val intOrd: Ord[Int] = new IntOrd +implicit def listOrd(implicit ev: Ord[T]): Ord[List[T]] = new ListOrd[T] +``` +The `lazy` modifier is applicable to unparameterized alias witnesses. If present, the resulting implicit val is lazy. For instance, +```scala +lazy witness intOrd2: Ord[Int] = new IntOrd +``` +would be equivalent to +```scala +lazy implicit val intOrd2: Ord[Int] = new IntOrd +``` + +## Implicit Conversions and Classes + +The only use cases that are not yet covered by the proposal are implicit conversions and implicit classes. We do not propose to use `witness` in place of `implicit` for these, since that would bring back the uncomfortable similarity between implicit conversions and parameterized implicit aliases. However, there is a way to drop implicit conversions entirely. Scala 3 already [defines](https://github.com/lampepfl/dotty/pull/2065) a class `ImplicitConverter` whose instances are available as implicit conversions. +```scala + abstract class ImplicitConverter[-T, +U] extends Function1[T, U] +``` +One can define all implicit conversions as witnesses of this class. E.g. +```scala +witness StringToToken for ImplicitConverter[String, Token] { + def apply(str: String): Token = new KeyWord(str) +} +``` +The fact that this syntax is more verbose than simple implicit defs could be a welcome side effect since it might dampen any over-enthusiasm for defining implicit conversions. + +That leaves implicit classes. Most use cases of implicit classes are probably already covered by extension methods. For the others, one could always fall back to a pair of a regular class and an `ImplicitConverter` witness. It would be good to do a survey to find out how many classes would be affected. + +## Summoning a Witness + +Besides `implicit`, there is also `implicitly`, a method defined in `Predef` that computes an implicit value for a given type. Keeping with the "witness" terminology, it seems apt to introduce the name `summon` for this operation. So `summon[T]` summons a witness for `T`, in the same way as `implicitly[T]` did. The definition of `summon` is straightforward: +```scala +def summon[T] with (x: T) = x +``` + +## Syntax + +The syntax changes for this page are summarized as follows: +``` +WitnessDef ::= ... + | id WitnessParams ‘:’ 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. diff --git a/docs/docs/reference/witnesses/witness-params.md b/docs/docs/reference/witnesses/witness-params.md index 3a17ad292cc2..643ee8344993 100644 --- a/docs/docs/reference/witnesses/witness-params.md +++ b/docs/docs/reference/witnesses/witness-params.md @@ -71,13 +71,6 @@ This scheme is essentially what MacWire does. MacWire was implemented as a macro I considered for a while an alternative design where the two notions of an implicit parameter (argument gets synthesized vs. parameter is itself available as an implicit value) are separated. This would allow a nicer expression of component assembly which would not require that dependencies are repeated in the witnesses. The most significant downside of the alternative design is that it's likely to induce choice fatigue. In most cases, implicit parameters should be available itself as a witness, so asking for an opt-in each time a parameter is defined became quickly tiresome. -## Summoning a Witness - -The `implicitly` method defined in `Predef` computes an implicit value for a given type. Keeping with the "witness" terminology, it seems apt to introduce the name `summon` for this operation. So `summon[T]` summons a witness for `T`, in the same way as `implicitly[T]` does. The definition of `summon` is straightforward: -```scala -def summon[T] with (x: T) = x -``` - ## Implicit Function Types and Closures Implicit function types are expressed using the new reserved operator `|=>`. Examples: diff --git a/docs/docs/reference/witnesses/witnesses.md b/docs/docs/reference/witnesses/witnesses.md index e876e77f88f9..b5321933fa76 100644 --- a/docs/docs/reference/witnesses/witnesses.md +++ b/docs/docs/reference/witnesses/witnesses.md @@ -123,76 +123,6 @@ witness ListOrd[T] with (_: Ord[T]) for List[Ord[T]] { ... } **Design note:** An alternative to the underscore syntax would be to allow the `name:` part to be left out entirely. I.e. it would then be `witness ListOrd[T] with (Ord[T]) for ...`. I am not yet sure which is preferable. -## Abstract and Alias Witnesses - -Witness definitions can be abstract. -As an example for an abstract witness consider the following fragment that's derived from Scala's Tasty extractor framework: -```scala -trait TastyAPI { - type Symbol - trait SymDeco { - def name(this sym: Symbol): Name - def tpe(this sym: Symbol): Type - } - witness symDeco: SymDeco -} -``` -Here, `symDeco` is available as a witness for the `SymDeco` trait but its actual implementation -is deferred to subclasses of the `TastyAPI` trait. - -An example of an alias witness would be an implementation of `symDeco` in terms of some internal compiler structure: -```scala -trait TastyImpl extends TastyAPI { - witness symDeco: SymDeco = compilerSymOps -} -``` -Note that the result type of an abstract or alias witness is introduced with a colon instead of a `for`. This seems more natural since it evokes the similarity to implicit parameters, whose type is also given following a `:`. It also avoids the syntactic ambiguity with a witness -for a class that does not add any new definitions. I.e. -```scala -witness a for C // concrete witness for class C, no definitions added -witness b: C // abstract witness for class C -``` -Further examples of alias witnesses: -```scala -witness ctx = outer.ctx -witness ctx: 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) -``` -As another example, if one had already defined classes `IntOrd` and `ListOrd`, witnesses for them could be defined as follows: -```scala -class IntOrd extends Ord[Int] { ... } -class ListOrd[T: Ord] extends Ord[List[T]] { ... } - -witness intOrd: Ord[Int] = new IntOrd -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 translate to abstract implicit defs. Alias witnesses translate to implicit defs if they are parameterized or to implicit vals otherwise. For instance, the witnesses defined so far in this section translate 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 f[T]: C[T] = new C[T] -implicit def g(implicit ctx: Context): D = new D(ctx) - -implicit val intOrd: Ord[Int] = new IntOrd -implicit def listOrd(implicit ev: Ord[T]): Ord[List[T]] = new ListOrd[T] -``` -The `lazy` modifier is applicable to unparameterized alias witnesses. If present, the resulting implicit val is lazy. For instance, -```scala -lazy witness intOrd2: Ord[Int] = new IntOrd -``` -would translate to -```scala -lazy implicit val intOrd2: Ord[Int] = new IntOrd -``` ## Witnesses as Typeclass Instances @@ -251,8 +181,6 @@ Here is the new syntax for witness definitions, seen as a delta from the [standa TmplDef ::= ... | ‘witness’ WitnessDef WitnessDef ::= [id] WitnessParams [‘for’ ConstrApps] [TemplateBody] - | id WitnessParams ‘:’ Type ‘=’ Expr - | id ‘=’ Expr WitnessParams ::= [DefTypeParamClause] {‘with’ ‘(’ [DefParams] ‘)} ``` The identifier `id` can be omitted only if either the `for` part or the template body is present. If the `for` part is missing, the template body must define at least one extension method. \ No newline at end of file diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 69217a0a816d..c6459ba7a9a3 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -48,6 +48,8 @@ sidebar: url: docs/reference/witnesses/witnesses.html - title: Witness Parameters url: docs/reference/witnesses/witness-params.html + - title: Replacing Implicits + url: docs/reference/witnesses/replacing-implicits.html - title: Discussion url: docs/reference/witnesses/discussion.html - title: Other New Features From 16abc13b11814da24062f3569019c37475751ab3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 11 Nov 2018 12:45:51 +0100 Subject: [PATCH 12/55] Parsing of contextual (`with`) parameters and arguments # Conflicts: # compiler/src/dotty/tools/dotc/parsing/Parsers.scala # docs/docs/internals/syntax.md --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 5 +- .../src/dotty/tools/dotc/core/Flags.scala | 4 +- .../dotty/tools/dotc/parsing/Parsers.scala | 90 +++++++---- .../dotty/tools/dotc/typer/Applications.scala | 1 + .../test/dotc/run-test-pickling.blacklist | 2 + docs/docs/internals/syntax.md | 4 +- .../reference/witnesses/witness-params.md | 2 +- docs/docs/reference/witnesses/witnesses.md | 2 +- tests/pos/reference/witnesses.scala | 145 ++++++++++++++++-- tests/run/witnesses-anonymous.scala | 36 ++--- tests/run/witnesses.scala | 34 ++-- 11 files changed, 241 insertions(+), 84 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index ff602bc3e7dc..f612790a4c37 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -106,7 +106,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree case class DependentTypeTree(tp: List[Symbol] => Type)(implicit @constructorOnly src: SourceFile) extends Tree - @sharable object EmptyTypeIdent extends Ident(tpnme.EMPTY)(NoSource) with WithoutTypeOrPos[Untyped] { + @sharable object EmptyTypeIdent extends Ident(tpnme.EMPTY) with WithoutTypeOrPos[Untyped] { override def isEmpty: Boolean = true } @@ -271,6 +271,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { */ val OriginalSymbol: Property.Key[Symbol] = new Property.Key + /** Property key for contextual Apply trees of the form `fn with arg` */ + val WithApply: Property.StickyKey[Unit] = new Property.StickyKey + // ------ Creation methods for untyped only ----------------- def Ident(name: Name)(implicit src: SourceFile): Ident = new Ident(name) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 7368d85484be..f7bad7265047 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -312,7 +312,6 @@ object Flags { final val Contravariant: FlagSet = typeFlag(21, "") final val Label: FlagSet = termFlag(21, "