diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 99c1040..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: scala - -jdk: - - openjdk8 - -script: - - sbt run - - sbt 'set scalaVersion := dottyLatestNightlyBuild.get' run diff --git a/README.md b/README.md index 3e6c916..a02223b 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,44 @@ -# Example sbt project that compiles using Dotty +# Example sbt Project that Compiles Using Dotty -[![Build Status](https://travis-ci.org/lampepfl/dotty-example-project.svg?branch=master)](https://travis-ci.org/lampepfl/dotty-example-project) +I (Mike Slinn, founder of [ScalaCourses.com](https://www.ScalaCourses.com)) fixed many significant bugs in the [upstream project](https://github.com/lampepfl/dotty-example-project) and added code examples for new major features that were missing. ## Usage -This is a normal sbt project, you can compile code with `sbt compile` and run it -with `sbt run`, `sbt console` will start a Dotty REPL. +This is a normal sbt project. +You can compile code with `sbt compile` and run it +with `sbt run` or `sbt runMain `. +`sbt console` will start a Dotty REPL. + +You can also execute just one example from the `sbt console` by evaluating the `test` method, for example we can run `Typeclasses.test`: + +``` +$ sbt console +scala> Typeclasses.test +sum("a", "b", "c"): abc +``` If compiling this example project fails, you probably have a global sbt plugin -that does not work with dotty, try to disable all plugins in -`~/.sbt/1.0/plugins` and `~/.sbt/1.0`. +that does not work with dotty; disable all plugins in +`~/.sbt/1.0/plugins` and `~/.sbt/1.0` by renaming those directories to something else. -### IDE support +## IDE Support -Dotty comes built-in with IDE support, to try it out see +> Dotty comes built-in with IDE support, to try it out see http://dotty.epfl.ch/docs/usage/ide-support.html -## Making a new Dotty project -The fastest way to start a new Dotty project is to use one of the following templates: -* [Simple Dotty project](https://github.com/lampepfl/dotty.g8) -* [Dotty project that cross-compiles with Scala 2](https://github.com/lampepfl/dotty-cross.g8) +Meh, not so much. +I found Atom with sbt running in a shell under `platformio-ide-terminal` worked best. + +## Making a New Dotty Project +> The fastest way to start a new Dotty project is to use one of the following templates: +> * [Simple Dotty project](https://github.com/lampepfl/dotty.g8) +> * [Dotty project that cross-compiles with Scala 2](https://github.com/lampepfl/dotty-cross.g8) -## Using Dotty in an existing project +I am not a fan of `giter8`. +There is no reason to introduce yet another obscure language. +[Try dottyTemplate](https://github.com/mslinn/dottyTemplate) instead. + +## Using Dotty in An Existing Project You will need to make the following adjustments to your build: @@ -30,15 +47,15 @@ You will need to make the following adjustments to your build: addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.3.4") ``` -### project/build.properties +### `project/build.properties` ```scala sbt.version=1.2.8 ``` -Older versions of sbt are not supported. - +Versions of sbt older than 1.2.8 are not supported. +Versions 1.3.3 and 1.3.4 do not work properly with Dotty. -### build.sbt +### `build.sbt` Any version number that starts with `0.` is automatically recognized as Dotty by the `sbt-dotty` plugin, you don't need to set up anything: @@ -46,15 +63,14 @@ the `sbt-dotty` plugin, you don't need to set up anything: scalaVersion := "0.20.0-RC1" ``` -#### Nightly builds +### Nightly Builds If the latest release of Dotty is missing a bugfix or feature you need, you may wish to use a nightly build. Look at the bottom of https://repo1.maven.org/maven2/ch/epfl/lamp/dotty_0.18/ to find the version number for the latest nightly build. Alternatively, you can set `scalaVersion := dottyLatestNightlyBuild.get` to always use the latest nightly build of dotty. -## Getting your project to compile with Dotty - +## Compiling Scala 2 Projects with Dotty When porting an existing project, it's a good idea to start out with the Scala 2 compatibility mode (note that this mode affects typechecking and thus may prevent some valid Dotty code from compiling) by adding to your `build.sbt`: @@ -66,9 +82,11 @@ scalacOptions ++= { if (isDotty.value) Seq("-language:Scala2") else Nil } Using the `isDotty` setting ensures that this option will only be set when compiling with Dotty. -A tool to port code from Scala 2.x to Dotty is currently in development at +> A tool to port code from Scala 2.x to Dotty is currently in development at https://github.com/scalacenter/scalafix +Actually, the truth is `scalafix` has not had any work done on it in a long time, and there is no indication when this important project will get the attention it deserves. I'm concerned that history will repeat itself and we'll get a last-minute hack job. + If your build contains dependencies that have only been published for Scala 2.x, you may be able to get them to work on Dotty by replacing: @@ -93,6 +111,4 @@ Alternatively, to set this setting on all your dependencies, you can use: ``` ## Discuss - -Feel free to come chat with us on the -[Dotty gitter](http://gitter.im/lampepfl/dotty)! +Chat on the [Dotty gitter](http://gitter.im/lampepfl/dotty) channel. diff --git a/build.sbt b/build.sbt index 88e1cb0..9140e8e 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,7 @@ -lazy val root = project - .in(file(".")) - .settings( - name := "dotty-example-project", - description := "Example sbt project that compiles using Dotty", - version := "0.1.0", - - scalaVersion := "0.20.0-RC1" - ) +description := "Example sbt project that compiles using Dotty" +logBuffered in Test := false +logLevel := Level.Warn +logLevel in test := Level.Info +name := "dotty-example-project" +scalaVersion := "0.20.0-RC1" +version := "0.1.0" diff --git a/project/build.properties b/project/build.properties index c0bab04..cb23649 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1,3 @@ -sbt.version=1.2.8 +# Sbt versions older than 1.1.4 are not supported with Dotty. +# Sbt versions 1.3.3 and 1.3.4 do not work properly with Dotty. +sbt.version=1.4.4 diff --git a/src/main/scala/AutoParamTupling.scala b/src/main/scala/AutoParamTupling.scala index b72bab8..30ec2a3 100644 --- a/src/main/scala/AutoParamTupling.scala +++ b/src/main/scala/AutoParamTupling.scala @@ -1,24 +1,14 @@ - -/** - * Automatic Tupling of Function Params: https://dotty.epfl.ch/docs/reference/other-new-features/auto-parameter-tupling.html - */ -object AutoParamTupling { - - def test: Unit = { - - /** - * In order to get thread safety, you need to put @volatile before lazy vals. - * https://dotty.epfl.ch/docs/reference/changed-features/lazy-vals.html - */ +/** Automatic Tupling of Function Params: https://dotty.epfl.ch/docs/reference/other-new-features/auto-parameter-tupling.html */ +@main def AutoParamTupling = + def test: Unit = + /** In order to get thread safety, you need to put @volatile before lazy vals. + * https://dotty.epfl.ch/docs/reference/changed-features/lazy-vals.html */ @volatile lazy val xs: List[String] = List("d", "o", "t", "t", "y") - /** - * Current behaviour in Scala 2.12.2 : + /** Current behaviour in Scala 2.12 : * error: missing parameter type * Note: The expected type requires a one-argument function accepting a 2-Tuple. - * Consider a pattern matching anonymous function, `{ case (s, i) => ... }` - */ + * Consider a pattern matching anonymous function, `{ case (s, i) => ... }` */ xs.zipWithIndex.map((s, i) => println(s"$i: $s")) - } -} \ No newline at end of file + test diff --git a/src/main/scala/ContextQueries.scala b/src/main/scala/ContextQueries.scala deleted file mode 100644 index 52873b0..0000000 --- a/src/main/scala/ContextQueries.scala +++ /dev/null @@ -1,48 +0,0 @@ - -import scala.concurrent.{ExecutionContext, Future} -import scala.util.Try - -/** - * Context Queries: - * - http://dotty.epfl.ch/docs/reference/contextual/query-types.html, - * - https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html - */ -object ContextQueries /* Formerly known as Implicit Function Types */ { - - object context { - // type alias Contextual - type Contextual[T] = (given ExecutionContext) => T - - // sum is expanded to sum(x, y)(ctx) - def asyncSum(x: Int, y: Int): Contextual[Future[Int]] = Future(x + y) - - def asyncMult(x: Int, y: Int)(given ctx: ExecutionContext) = Future(x * y) - } - - object parse { - - type Parseable[T] = (given ImpliedInstances.StringParser[T]) => Try[T] - - def sumStrings(x: String, y: String): Parseable[Int] = { - val parser = implicitly[ImpliedInstances.StringParser[Int]] - val tryA = parser.parse(x) - val tryB = parser.parse(y) - - for { - a <- tryA - b <- tryB - } yield a + b - } - } - - def test: Unit = { - - import ExecutionContext.Implicits.global - context.asyncSum(3, 4).foreach(println) - context.asyncMult(3, 4).foreach(println) - - println(parse.sumStrings("3", "4")) - println(parse.sumStrings("3", "a")) - } - -} diff --git a/src/main/scala/ContextQueries1.scala b/src/main/scala/ContextQueries1.scala new file mode 100644 index 0000000..049f33a --- /dev/null +++ b/src/main/scala/ContextQueries1.scala @@ -0,0 +1,45 @@ + +import scala.concurrent.{ExecutionContext, Future} +import scala.util.Try + +/** Context Queries (Formerly known as Implicit Function Types): + * - Implicit functions are functions that only have implicit parameters. + * - Their types are implicit function types. + * - http://dotty.epfl.ch/docs/reference/contextual/implicit-function-types.html, + * - Old syntax: https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html */ +@main def ContextQueries1 = + object context with + // type alias Contextual + type Contextual[T] = (given ExecutionContext) => T + + // sum is expanded to sum(x, y)(ctx) + def asyncSum(x: Int, y: Int): Contextual[Future[Int]] = Future(x + y) + + def asyncMult(x: Int, y: Int) + (given ctx: ExecutionContext): Contextual[Future[Int]] = + Future(x * y) + + object parse with + type Parseable[T] = (given Delegates.StringParser[T]) => Try[T] + + def sumStrings(x: String, y: String): Parseable[Int] = { + val parser = implicitly[Delegates.StringParser[Int]] + val tryA = parser.parse(x) + val tryB = parser.parse(y) + + for { + a <- tryA + b <- tryB + } yield a + b + } + + def test: Unit = + import ExecutionContext.Implicits.global + + context.asyncSum(3, 4).foreach(x => println("asyncSum: " + x)) + context.asyncMult(3, 4).foreach(x => println("asyncMult: " + x)) + + println("""parse.sumStrings("3", "4"): """ + parse.sumStrings("3", "4")) + println("""parse.sumStrings("3", "a"): """ + parse.sumStrings("3", "a")) + + test diff --git a/src/main/scala/ContextQueries2.scala b/src/main/scala/ContextQueries2.scala new file mode 100644 index 0000000..fd3fe8e --- /dev/null +++ b/src/main/scala/ContextQueries2.scala @@ -0,0 +1,25 @@ +/** This example combines opaque aliases, implicit function types, and extension methods to provide + * a zero-overhead abstraction at runtime. + * See http://dotty.epfl.ch/docs/reference/contextual/implicit-function-types.html#example-postconditions */ +object PostConditions + opaque type WrappedResult[T] = T + + def result[T](given r: WrappedResult[T]): T = r + + /** This extension method accepts an implicit function type called `condition`, + * which receives the given instance of type `WrappedResult[T]``. + * The given `WrappedResult` instance is passed to the result method. + * `WrappedResult` is an opaque type alias, so its values need not be boxed or unboxed. + * Because `ensuring` is added as an extension method, its argument also does not need boxing. */ + def [T](x: T) ensuring(condition: (given WrappedResult[T]) => Boolean): T = + assert(condition(given x)) + x + +@main def ContextQueries2 = + import PostConditions.{ensuring, result} + + def test: Unit = + val s = List(1, 2, 3).sum.ensuring(result == 6) + println("Yes, the list sums to 6.") + + test diff --git a/src/main/scala/Conversion.scala b/src/main/scala/Conversion.scala index 06b013e..686ec0e 100644 --- a/src/main/scala/Conversion.scala +++ b/src/main/scala/Conversion.scala @@ -1,37 +1,24 @@ import scala.language.implicitConversions -/** - * Conversions: http://dotty.epfl.ch/docs/reference/contextual/conversions.html - */ -object Conversion { - - case class IntWrapper(a: Int) extends AnyVal - case class DoubleWrapper(b: Double) extends AnyVal - - def convert[T, U](x: T)(given converter: Conversion[T, U]): U = converter(x) - - given IntWrapperToDoubleWrapper: Conversion[IntWrapper, DoubleWrapper] = new Conversion[IntWrapper, DoubleWrapper] { - override def apply(i: IntWrapper): DoubleWrapper = new DoubleWrapper(i.a.toDouble) - } - - def useConversion(given f: Conversion[IntWrapper, DoubleWrapper]) = { +case class IntWrapper(a: Int) extends AnyVal +case class DoubleWrapper(b: Double) extends AnyVal + +/** Conversions: http://dotty.epfl.ch/docs/reference/contextual/conversions.html */ +@main def Conversion = + def convert[T, U](t: T) + (given converter: Conversion[T, U]): U = converter(t) + + given IntWrapperToDoubleWrapper: Conversion[IntWrapper, DoubleWrapper] = + new Conversion[IntWrapper, DoubleWrapper] with + override def apply(i: IntWrapper): DoubleWrapper = new DoubleWrapper(i.a.toDouble) + + def useConversion(given f: Conversion[IntWrapper, DoubleWrapper]) = val y: IntWrapper = new IntWrapper(4) val x: DoubleWrapper = y x - } - /* Not working anymore. - def useConversion(implicit f: A => B) = { - val y: A = ... - val x: B = a // error under Dotty - } - */ - - def test: Unit = { + def test: Unit = println(useConversion) println(convert(new IntWrapper(42))) - } - - -} + test diff --git a/src/main/scala/Delegates.scala b/src/main/scala/Delegates.scala new file mode 100644 index 0000000..4403f97 --- /dev/null +++ b/src/main/scala/Delegates.scala @@ -0,0 +1,35 @@ +import scala.util.{Success, Try} + +/** Delegates (formerly known as Implied Instances): + * - https://dotty.epfl.ch/docs/reference/contextual/delegates.html + * - https://dotty.epfl.ch/docs/reference/contextual/derivation.html*/ +object Delegates with + sealed trait StringParser[A] + def parse(s: String): Try[A] + + object StringParser with + def apply[A](given parser: StringParser[A]): StringParser[A] = parser + + private def baseParser[A](f: String ⇒ Try[A]): StringParser[A] = + new StringParser[A] { + override def parse(s: String): Try[A] = f(s) + } + + given stringParser: StringParser[String] = baseParser(Success(_)) + given intParser: StringParser[Int] = baseParser(s ⇒ Try(s.toInt)) + + given optionParser[A](given parser: => StringParser[A]): StringParser[Option[A]] = + new StringParser[Option[A]] { + override def parse(s: String): Try[Option[A]] = s match + case "" ⇒ Success(None) // implicit parser not used. + case str ⇒ parser.parse(str).map(Some(_)) // implicit parser is evaluated here + } + + def test: Unit = + val spoi = implicitly[StringParser[Option[Int]]] + println(spoi.parse("21")) + println(spoi.parse("")) + println(spoi.parse("21a")) + println(StringParser.optionParser[Int].parse("42")) + + test diff --git a/src/main/scala/EnumTypes.scala b/src/main/scala/EnumTypes.scala index 7996626..9b5ceae 100644 --- a/src/main/scala/EnumTypes.scala +++ b/src/main/scala/EnumTypes.scala @@ -1,16 +1,18 @@ -/** - * Enum Types: http://dotty.epfl.ch/docs/reference/enums/adts.html - */ -object EnumTypes { - - enum ListEnum[+A] { +/** Proper Scala enums (Scala 2 Enumerations are horrible). + * * Interoperate with Java enums + * * Support Algebraic Data Types (ADTs) because they accept type parameters, and can be Generalized ADTs (GADTs). + * See http://dotty.epfl.ch/docs/reference/enums/adts.html */ +@main def EnumTypes = + enum ListEnum[+A] case Cons(h: A, t: ListEnum[A]) case Empty - } - enum Planet(mass: Double, radius: Double) { + /** This looks a lot like the [old Java enum example](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html) */ + enum Planet(mass: Double, radius: Double) with private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity case Mercury extends Planet(3.303e+23, 2.4397e6) @@ -21,22 +23,19 @@ object EnumTypes { case Saturn extends Planet(5.688e+26, 6.0268e7) case Uranus extends Planet(8.686e+25, 2.5559e7) case Neptune extends Planet(1.024e+26, 2.4746e7) - } - - def test: Unit = { + def test: Unit = val emptyList = ListEnum.Empty val list = ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) println(emptyList) - println(s"${list}\n") + println(s"$list\n") - def calculateEarthWeightOnPlanets(earthWeight: Double) = { + def calculateEarthWeightOnPlanets(earthWeight: Double): Unit = val mass = earthWeight/Planet.Earth.surfaceGravity for (p <- Planet.values) println(s"Your weight on $p is ${p.surfaceWeight(mass)}") - } + () calculateEarthWeightOnPlanets(80) - } -} + test diff --git a/src/main/scala/ExtensionClasses.scala b/src/main/scala/ExtensionClasses.scala new file mode 100644 index 0000000..d70b872 --- /dev/null +++ b/src/main/scala/ExtensionClasses.scala @@ -0,0 +1,31 @@ +/** Easy way to add methods to existing classes + * See https://dotty.epfl.ch/docs/reference/contextual/extension-methods-new.html */ +object ExtensionClasses1 extends App { + case class Circle(x: Double, y: Double, radius: Double) + + // New Scala 3 way, using an extension method: + def (c: Circle) circumference: Double = c.radius * math.Pi * 2 + + val circle = Circle(0, 0, 0.5) + println(s"circumference = ${ circle.circumference }") + + assert(circle.circumference == circumference(circle)) + + // Old Scala 2 way, using an implicit class: + //implicit class CircleOps(circle: Circle) extends AnyVal { + // def circumference = circle.radius * Pi * 2 + //} +} + +object ExtensionClasses2 extends App { + trait StringSeqOps { + def (xs: Seq[String]) longestStrings = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } + } + + given ops1: StringSeqOps + + println(s"""List("here", "is", "a", "list").longestStrings: """ + List("here", "is", "a", "list").longestStrings) +} diff --git a/src/main/scala/ImpliedInstances.scala b/src/main/scala/ImpliedInstances.scala deleted file mode 100644 index e4f0433..0000000 --- a/src/main/scala/ImpliedInstances.scala +++ /dev/null @@ -1,39 +0,0 @@ -import scala.util.{Success, Try} - -/** - * Implied Instances: - * - https://dotty.epfl.ch/docs/reference/contextual/instance-defs.html - */ -object ImpliedInstances { - - sealed trait StringParser[A] { - def parse(s: String): Try[A] - } - - object StringParser { - - def apply[A](given parser: StringParser[A]): StringParser[A] = parser - - private def baseParser[A](f: String ⇒ Try[A]): StringParser[A] = new StringParser[A] { - override def parse(s: String): Try[A] = f(s) - } - - given stringParser: StringParser[String] = baseParser(Success(_)) - given intParser: StringParser[Int] = baseParser(s ⇒ Try(s.toInt)) - - given optionParser[A](given parser: => StringParser[A]): StringParser[Option[A]] = new StringParser[Option[A]] { - override def parse(s: String): Try[Option[A]] = s match { - case "" ⇒ Success(None) // implicit parser not used. - case str ⇒ parser.parse(str).map(x ⇒ Some(x)) // implicit parser is evaluated at here - } - } - } - - def test: Unit = { - println(implicitly[StringParser[Option[Int]]].parse("21")) - println(implicitly[StringParser[Option[Int]]].parse("")) - println(implicitly[StringParser[Option[Int]]].parse("21a")) - - println(implicitly[StringParser[Option[Int]]](StringParser.optionParser[Int]).parse("42")) - } -} diff --git a/src/main/scala/IntersectionTypes.scala b/src/main/scala/IntersectionTypes.scala index 9ebeed4..6fd10d2 100644 --- a/src/main/scala/IntersectionTypes.scala +++ b/src/main/scala/IntersectionTypes.scala @@ -1,34 +1,28 @@ /** * Intersection Types: https://dotty.epfl.ch/docs/reference/new-types/intersection-types.html */ -object IntersectionTypes { - - sealed trait X { +@main def IntersectionTypes = + sealed trait X def x: Double def tpe: X - } - sealed trait Y { + sealed trait Y def y: Double def tpe: Y - } + // The compiler treats P and PP as equivalent and interchangeable types type P = Y & X type PP = X & Y - final case class Point(x: Double, y: Double) extends X with Y { - override def tpe: X & Y = ??? - } - - def test: Unit = { + final case class Point(x: Double, y: Double) extends X with Y + override def tpe: P = ??? - def euclideanDistance(p1: X & Y, p2: X & Y) = { + def test: Unit = + def euclideanDistance(p1: P, p2: P) = Math.sqrt(Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2)) - } val p1: P = Point(3, 4) val p2: PP = Point(6, 8) println(euclideanDistance(p1, p2)) - } -} + test diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index 18aed26..34a17c4 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -1,40 +1,25 @@ - -object Main { - - def main(args: Array[String]): Unit = { - - runExample("Trait Params")(TraitParams.test) - - runExample("Enum Types")(EnumTypes.test) - - runExample("Context Queries")(ContextQueries.test) - - runExample("Implied Instances")(ImpliedInstances.test) - - runExample("Conversion")(Conversion.test) - - runExample("Union Types")(UnionTypes.test) - - runExample("Intersection Types")(IntersectionTypes.test) - - runExample("Type Lambda")(TypeLambdas.test) - - runExample("Multiversal Equality")(MultiversalEquality.test) - - runExample("Named Type Arguments")(NamedTypeArguments.test) - - runExample("Auto Param Tupling")(AutoParamTupling.test) - - runExample("Structural Types")(StructuralTypes.test) - - runExample("Pattern Matching")(PatternMatching.test) - - } - - private def runExample(name: String)(f: => Unit) = { - println(Console.MAGENTA + s"$name example:" + Console.RESET) - f - println() - } - -} +@main def Main = + runExample("New Syntax")(SyntaxNew) + runExample("Trait Params")(TraitParams) + runExample("Typeclasses")(Typeclasses) + runExample("OpaqueTypes 1")(OpaqueTypes1) + runExample("Delegates")(Delegates) + runExample("Context Queries 1")(ContextQueries1) + runExample("Intersection Types")(IntersectionTypes) + runExample("Context Queries 2")(ContextQueries2) + runExample("OpaqueTypes 2")(OpaqueTypes2) + runExample("Type Lambdas")(TypeLambdas) + runExample("Enum Types")(EnumTypes) + runExample("Conversion")(Conversion) + runExample("Multiversal Equality")(MultiversalEquality) + runExample("Named Type Arguments")(NamedTypeArguments) + runExample("Automatic Functional Parameter Tupling")(AutoParamTupling) + runExample("Structural Types")(StructuralTypes) + runExample("Pattern Matching")(PatternMatching) + runExample("Union Types")(UnionTypes) + +private def runExample(name: String) + (f: => Unit) = + println(Console.MAGENTA + s"$name example:" + Console.RESET) + f + println() diff --git a/src/main/scala/MultiversalEquality.scala b/src/main/scala/MultiversalEquality.scala index 5152bd7..056e50e 100644 --- a/src/main/scala/MultiversalEquality.scala +++ b/src/main/scala/MultiversalEquality.scala @@ -4,22 +4,21 @@ import scala.language.strictEquality * Multiversal Equality: https://dotty.epfl.ch/docs/reference/contextual/multiversal-equality.html * scala.Eq definition: https://github.com/lampepfl/dotty/blob/master/library/src/scala/Eql.scala */ -object MultiversalEquality { - - def test: Unit = { - +@main def MultiversalEquality = + def test: Unit = // Values of types Int and String cannot be compared with == or !=, // unless we add the derived delegate instance like: given Eql[Int, String] = Eql.derived - println(3 == "3") + println("3 == \"3\": " + (3 == "3")) // By default, all numbers are comparable, because of; // implicit def eqlNumber: Eql[Number, Number] = derived - println(3 == 5.1) + println("3 == 5.1: " + (3 == 5.1)) - // By default, all Sequences are comparable, because of; + // By default, all Sequences are comparable (except Array), because of; // implicit def eqlSeq[T, U](implicit eq: Eql[T, U]): Eql[GenSeq[T], GenSeq[U]] = derived - println(List(1, 2) == Vector(1, 2)) + // This fails, why? + println("List(1, 2) == Vector(1, 2)): " + (List(1, 2) == Vector(1, 2))) class A(a: Int) class B(b: Int) @@ -27,12 +26,15 @@ object MultiversalEquality { val a = new A(4) val b = new B(4) + // Two arrays in Scala do not compare equal so this is illegal code that must not compile? + //println("Array(1, 2) == Array(1, 2): " + (Array(1, 2) == Array(1, 2))) + // scala.language.strictEquality is enabled, therefore we need some extra delegate instances // to compare instances of A and B. given Eql[A, B] = Eql.derived given Eql[B, A] = Eql.derived - println(a != b) - println(b == a) - } -} + println("a != b: " + (a != b)) + println("b == a: " + (b == a)) + + test diff --git a/src/main/scala/NamedTypeArguments.scala b/src/main/scala/NamedTypeArguments.scala index 85cee9a..4aa20e1 100644 --- a/src/main/scala/NamedTypeArguments.scala +++ b/src/main/scala/NamedTypeArguments.scala @@ -1,20 +1,17 @@ +/** Named Type Arguments: https://dotty.epfl.ch/docs/reference/other-new-features/named-typeargs.html */ +@main def NamedTypeArguments = + trait Functor[F[_]] + def map[A, B](fa: F[A]) + (f: A => B): F[B] -/** - * Named Type Arguments: https://dotty.epfl.ch/docs/reference/other-new-features/named-typeargs.html - */ -object NamedTypeArguments { + implicit object listFunctor extends Functor[List] with + override def map[A, B](fa: List[A]) + (f: A => B): List[B] = fa.map(f) - trait Functor[F[_]] { - def map[A, B](fa: F[A])(f: A => B): F[B] - } - - implicit object listFunctor extends Functor[List] { - override def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f) - } - - def test: Unit = { - - def fmap[F[_], A, B](fa: F[A])(f: A => B)(implicit F: Functor[F]): F[B] = F.map(fa)(f) + def test: Unit = + def fmap[F[_], A, B](fa: F[A]) + (f: A => B) + (implicit F: Functor[F]): F[B] = F.map(fa)(f) val result: List[Int] = fmap[F = List, A = Int, B = Int](List(1,2,3))(i => i + 1) @@ -26,6 +23,4 @@ object NamedTypeArguments { println(compile) - } - -} \ No newline at end of file + test diff --git a/src/main/scala/OpaqueTypes1.scala b/src/main/scala/OpaqueTypes1.scala new file mode 100644 index 0000000..dd65488 --- /dev/null +++ b/src/main/scala/OpaqueTypes1.scala @@ -0,0 +1,27 @@ +/** Opaque types aliases provide type abstraction without *any* runtime overhead. + * They replace Scala 2 value types. + * See https://dotty.epfl.ch/docs/reference/other-new-features/opaques.html */ +@main def OpaqueTypes1 = + object Logarithms with + opaque type Logarithm = Double + + object Logarithm with + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // Extension methods define opaque types' public APIs + given logarithmOps: with + def (x: Logarithm) toDouble: Double = math.exp(x) + def (x: Logarithm) + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y)) + def (x: Logarithm) * (y: Logarithm): Logarithm = Logarithm(x + y) + + def test: Unit = + import Logarithms._ + + println("Logarithm(1.0) * Logarithm(2.0): " + (Logarithm(1.0) * Logarithm(2.0))) + println("Logarithm(1.0) + Logarithm(2.0): " + (Logarithm(1.0) + Logarithm(2.0))) + + test diff --git a/src/main/scala/OpaqueTypes2.scala b/src/main/scala/OpaqueTypes2.scala new file mode 100644 index 0000000..fc120b5 --- /dev/null +++ b/src/main/scala/OpaqueTypes2.scala @@ -0,0 +1,30 @@ +@main def OpaqueTypes2 = + object Access with + opaque type Permissions = Int + opaque type PermissionChoice = Int + + /** `Permission`'s upper bound is `Permissions & PermissionChoice`. + * Thus `Permission` is universally known to be a subtype of `Permissions` and `PermissionChoice`. */ + opaque type Permission <: Permissions & PermissionChoice = Int + + def (x: Permissions) & (y: Permissions): Permissions = x & y + def (x: PermissionChoice) | (y: PermissionChoice): PermissionChoice = x | y + def (x: Permissions) is (y: Permissions) = (x & y) == y + def (x: Permissions) isOneOf (y: PermissionChoice) = (x & y) != 0 + + val NoPermission: Permission = 0 + val ReadOnly: Permission = 1 + val WriteOnly: Permission = 2 + val ReadWrite: Permissions = ReadOnly | WriteOnly + val ReadOrWrite: PermissionChoice = ReadOnly | WriteOnly + + def test: Unit = + import Access._ + + case class Item(rights: Permissions) + + val x = Item(ReadOnly) + assert( ! x.rights.is(ReadWrite) ) + assert( x.rights.isOneOf(ReadOrWrite) ) + + test diff --git a/src/main/scala/PatternMatching.scala b/src/main/scala/PatternMatching.scala index 8e64e87..df8fd2c 100644 --- a/src/main/scala/PatternMatching.scala +++ b/src/main/scala/PatternMatching.scala @@ -1,20 +1,11 @@ - -/** - * Pattern Matching: https://dotty.epfl.ch/docs/reference/changed-features/pattern-matching.html - */ -object PatternMatching { - - object booleanPattern { - - object Even { +/** Pattern Matching: https://dotty.epfl.ch/docs/reference/changed-features/pattern-matching.html */ +@main def PatternMatching = + object booleanPattern with + object Even with def unapply(s: String): Boolean = s.length % 2 == 0 - } - - } - - object productPattern { - class Person(name: String, age: Int) extends Product { + object productPattern with + class Person(name: String, age: Int) extends Product // if we not define that, it will give compile error. // we change the order def _1 = age @@ -24,42 +15,27 @@ object PatternMatching { def canEqual(that: Any): Boolean = ??? def productArity: Int = ??? def productElement(n: Int): Any = ??? - } - object Person { + object Person with def unapply(a: (String, Int)): Person = new Person(a._1, a._2) - } - - } - - object seqPattern { + object seqPattern with // adapted from http://danielwestheide.com/blog/2012/11/28/the-neophytes-guide-to-scala-part-2-extracting-sequences.html - object Names { - def unapplySeq(name: String): Option[Seq[String]] = { + object Names with + def unapplySeq(name: String): Option[Seq[String]] = val names = name.trim.split(" ") if (names.size < 2) None else Some(names.last :: names.head :: names.drop(1).dropRight(1).toList) - } - } - } - - object namePattern { - - class Name(val name: String) { + object namePattern with + class Name(val name: String) def get: String = name def isEmpty = name.isEmpty - } - object Name { + object Name with def unapply(s: String): Name = new Name(s) - } - - } def test: Unit = { - import booleanPattern._ "even" match { @@ -78,26 +54,23 @@ object PatternMatching { println(containsConsecutive(List(1, 2, 3, 3, 5))) import productPattern._ - ("john", 42) match { + ("john", 42) match case Person(n, a) => println(s"name: $n, age: $a") - } import seqPattern._ - def greet(fullName: String) = fullName match { + def greet(fullName: String) = fullName match case Names(lastName, firstName, _: _*) => "Good morning, " + firstName + " " + lastName + "!" case _ => "Welcome! Please make sure to fill in your name!" - } println(greet("Alan Turing")) println(greet("john")) println(greet("Wolfgang Amadeus Mozart")) import namePattern._ - "alice" match { + "alice" match case Name(n) => println(s"name is $n") case _ => println("empty name") - } - } -} \ No newline at end of file + + test diff --git a/src/main/scala/StructuralTypes.scala b/src/main/scala/StructuralTypes.scala index 4eb60a4..8f6c8bd 100644 --- a/src/main/scala/StructuralTypes.scala +++ b/src/main/scala/StructuralTypes.scala @@ -1,28 +1,21 @@ - -/** - * Structural Types: https://dotty.epfl.ch/docs/reference/changed-features/structural-types.html - */ -object StructuralTypes { - - case class Record(elems: (String, Any)*) extends Selectable { +/** Structural Types: https://dotty.epfl.ch/docs/reference/changed-features/structural-types.html */ +@main def StructuralTypes = + case class Record(elems: (String, Any)*) extends Selectable def selectDynamic(name: String): Any = elems.find(_._1 == name).get._2 - } - type Person = Record { + type Person = Record with val name: String val age: Int - } val person = Record("name" -> "Emma", "age" -> 42, "salary" -> 320L).asInstanceOf[Person] val invalidPerson = Record("name" -> "John", "salary" -> 42).asInstanceOf[Person] - def test: Unit = { + def test: Unit = println(person.name) println(person.age) - println(invalidPerson.name) // age field is java.util.NoSuchElementException: None.get //println(invalidPerson.age) - } -} \ No newline at end of file + + test diff --git a/src/main/scala/SyntaxNew.scala b/src/main/scala/SyntaxNew.scala new file mode 100644 index 0000000..8e4ced0 --- /dev/null +++ b/src/main/scala/SyntaxNew.scala @@ -0,0 +1,31 @@ +/** The new and old syntaxes can be mixed freely in one file. + * See https://dotty.epfl.ch/blog/2019/08/30/18th-dotty-milestone-release.html */ + +@main def SyntaxNew = + println("Hello from SyntaxNew") + + def test: Unit = + val xs = 0 to 3 + val xsFiltered = for x <- xs if x > 1 yield x + for + x <- xsFiltered + y <- xsFiltered + do println(s"$x * $y = ${x * y}") + + test + +/** To run, type `runMain testIf Monday` */ +@main def testIf(day: String) = + if day == "Sunday" || day == "Saturday" + then println("Today is a weekend, hooray!") + else println(s"Today is a workday.") + + /** To run, type `runMain testWhile 3` */ +@main def testWhile(bound: Int) = + var x = 0 + + def incrementX() = + x += 1 + println(x) + + while x <= bound do incrementX() diff --git a/src/main/scala/TraitParams.scala b/src/main/scala/TraitParams.scala index 5d44099..06c36e3 100644 --- a/src/main/scala/TraitParams.scala +++ b/src/main/scala/TraitParams.scala @@ -1,21 +1,21 @@ -/** - * Trait Parameters: https://dotty.epfl.ch/docs/reference/other-new-features/trait-parameters.html - */ -object TraitParams { - +/** Trait Parameters: https://dotty.epfl.ch/docs/reference/other-new-features/trait-parameters.html */ +@main def TraitParams = trait Base(val msg: String) + class A extends Base("Hello") - class B extends Base("Dotty!") + class B extends Base("Dotty") + + /** @param msgs varargs union type (sequence of A or B) */ + def printMessages1(msgs: (A | B)*): Unit = println(msgs.map(_.msg).mkString(" ") + "!") + - // Union types only exist in Dotty, so there's no chance that this will accidentally be compiled with Scala 2 - private def printMessages(msgs: (A | B)*) = println(msgs.map(_.msg).mkString(" ")) + case class C(override val msg: String) extends Base(msg) + case class D(override val msg: String) extends Base(msg) - def test: Unit = { + def printMessages2(msgs: (C | D)*): Unit = println(msgs.map(_.msg).mkString(" ") + "!") - printMessages(new A, new B) + def test: Unit = + printMessages1(new A, new B) + printMessages2(C("Goodbye"), D("cruel world")) - // Sanity check the classpath: this won't run if the dotty jar is not present. - val x: Int => Int = z => z - x(1) - } -} + test diff --git a/src/main/scala/TypeLambdas.scala b/src/main/scala/TypeLambdas.scala index bcbb5b8..3de219f 100644 --- a/src/main/scala/TypeLambdas.scala +++ b/src/main/scala/TypeLambdas.scala @@ -1,19 +1,14 @@ -/** - * Type Lambdas: https://dotty.epfl.ch/docs/reference/new-types/type-lambdas.html - */ -object TypeLambdas { - +/** Type Lambdas: https://dotty.epfl.ch/docs/reference/new-types/type-lambdas.html */ +@main def TypeLambdas = type T[+X, Y] = Map[Y, X] type Tuple = [X] =>> (X, X) - def test: Unit = { - + def test: Unit = val m: T[String, Int] = Map(1 -> "1") println(m) val tuple: Tuple[String] = ("a", "b") println(tuple) - } -} + test diff --git a/src/main/scala/Typeclasses.scala b/src/main/scala/Typeclasses.scala new file mode 100644 index 0000000..fd0c876 --- /dev/null +++ b/src/main/scala/Typeclasses.scala @@ -0,0 +1,24 @@ +/** Extension methods provide a nice syntax for typeclasses + * * Removes need for dangerous implicit conversions + * * Emphasize intent over mechanism + * * Better error messages than generated from implicits + * * Implicit conversions will go away + * See https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates */ +@main def Typeclasses = + trait SemiGroup[T] with + def (x: T) combine (y: T): T + + trait Monoid [T] extends SemiGroup[T] with + def unit: T + + given Monoid[String] with + def (x: String) combine (y: String) = x.concat(y) + def unit = "" + + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) + + def test: Unit = + println("""sum("a", "b", "c"): """ + sum(List("a", "b", "c"))) + + test diff --git a/src/main/scala/UnionTypes.scala b/src/main/scala/UnionTypes.scala index ab3e365..1e8c458 100644 --- a/src/main/scala/UnionTypes.scala +++ b/src/main/scala/UnionTypes.scala @@ -1,45 +1,45 @@ -/** - * Union Types: https://dotty.epfl.ch/docs/reference/new-types/union-types.html - */ -object UnionTypes { - +/** Union types (aka sum types): + * * Like C structs + * * No boxing/unboxing overhead + * * Works with singleton types + * * Good for Scala/JavaScript interoperability + * https://dotty.epfl.ch/docs/reference/new-types/union-types.html */ +@main def UnionTypes = sealed trait Division final case class DivisionByZero(msg: String) extends Division final case class Success(double: Double) extends Division - // You can create type aliases for your union types (sum types). + // Type aliases can use union types type DivisionResult = DivisionByZero | Success sealed trait List[+A] - final case class Empty() extends List[Nothing] final case class Cons[+A](h: A, t: List[A]) extends List[A] + final class Empty extends List[Nothing] + override def toString: String = "Empty" - private def safeDivide(a: Double, b: Double): DivisionResult = { + def safeDivide(a: Double, b: Double): DivisionResult = if (b == 0) DivisionByZero("DivisionByZeroException") else Success(a / b) - } - private def either(division: Division) = division match { + def either(division: Division) = division match case DivisionByZero(m) => Left(m) case Success(d) => Right(d) - } - - def test: Unit = { + def test: Unit = val divisionResultSuccess: DivisionResult = safeDivide(4, 2) - // commutative + // Commutative val divisionResultFailure: Success | DivisionByZero = safeDivide(4, 0) - // calling `either` function with union typed value. - println(either(divisionResultSuccess)) + // Calling `either` function with union typed value. + println("either(divisionResultSuccess): " + either(divisionResultSuccess)) - // calling `either` function with union typed value. - println(either(divisionResultFailure)) + // Calling `either` function with union typed value. + println("either(divisionResultFailure): " + either(divisionResultFailure)) val list: Cons[Int] | Empty = Cons(1, Cons(2, Cons(3, Empty()))) + println("list: " + list) + val emptyList: Empty | Cons[Any] = Empty() - println(list) - println(emptyList) + println("emptyList: " + emptyList) - } -} + test