├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── clean.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── build.sbt ├── project └── build.properties └── src ├── main └── scala │ ├── ContextFunctions.scala │ ├── Conversion.scala │ ├── EnumTypes.scala │ ├── GivenInstances.scala │ ├── IntersectionTypes.scala │ ├── Main.scala │ ├── MultiversalEquality.scala │ ├── ParameterUntupling.scala │ ├── PatternMatching.scala │ ├── StructuralTypes.scala │ ├── TraitParams.scala │ ├── TypeLambdas.scala │ └── UnionTypes.scala └── test └── scala └── MySuite.scala /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "tuesday" 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | env: 8 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 9 | 10 | jobs: 11 | run: 12 | name: Build and Run 13 | strategy: 14 | matrix: 15 | java-version: [8, 11, 17, 21] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout current branch (full) 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Setup Java 24 | uses: actions/setup-java@v4 25 | with: 26 | distribution: temurin 27 | java-version: ${{ matrix.java-version }} 28 | cache: sbt 29 | 30 | - uses: sbt/setup-sbt@v1 31 | 32 | - name: Run the project 33 | run: sbt run 34 | 35 | - name: Run the unit tests 36 | run: sbt test 37 | -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | run: | 21 | # Customize those three lines with your repository and credentials: 22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 23 | 24 | # A shortcut to call GitHub API. 25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 26 | 27 | # A temporary file which receives HTTP response headers. 28 | TMPFILE=/tmp/tmp.$$ 29 | 30 | # An associative array, key: artifact name, value: number of artifacts of that name. 31 | declare -A ARTCOUNT 32 | 33 | # Process all artifacts on this repository, loop on returned "pages". 34 | URL=$REPO/actions/artifacts 35 | while [[ -n "$URL" ]]; do 36 | 37 | # Get current page, get response headers in a temporary file. 38 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 39 | 40 | # Get URL of next page. Will be empty if we are at the last page. 41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 42 | rm -f $TMPFILE 43 | 44 | # Number of artifacts on this page: 45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 46 | 47 | # Loop on all artifacts on this page. 48 | for ((i=0; $i < $COUNT; i++)); do 49 | 50 | # Get name of artifact and count instances of this name. 51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 53 | 54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 57 | ghapi -X DELETE $REPO/actions/artifacts/$id 58 | done 59 | done -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.class 3 | *.log 4 | *~ 5 | 6 | # sbt specific 7 | dist/* 8 | target/ 9 | lib_managed/ 10 | src_managed/ 11 | project/boot/ 12 | project/plugins/project/ 13 | project/local-plugins.sbt 14 | .history 15 | .bsp 16 | .metals 17 | .bloop 18 | 19 | # Scala-IDE specific 20 | .scala_dependencies 21 | .cache 22 | .classpath 23 | .project 24 | .settings 25 | classes/ 26 | 27 | # idea 28 | .idea 29 | .idea_modules 30 | /.worksheet/ 31 | 32 | # Dotty-IDE 33 | .dotty-ide-artifact 34 | .dotty-ide.json 35 | 36 | # Visual Studio Code 37 | .vscode 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example sbt project that compiles using Scala 3 2 | 3 | [![Continuous Integration](https://github.com/scala/scala3-example-project/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/scala/scala3-example-project/actions/workflows/ci.yml) 4 | 5 | ## Usage 6 | 7 | This is a normal sbt project. You can start the sbt shell using `sbt` then compile code with `compile`, run the main 8 | method with `run`, run the tests with `test` and start a REPL using `console`. 9 | 10 | If compiling this example project fails, you probably have a global sbt plugin 11 | that does not work with Scala 3. You might try disabling plugins in 12 | `~/.sbt/1.0/plugins` and `~/.sbt/1.0`. 13 | 14 | ## Making a new Scala 3 project 15 | 16 | The fastest way to start a new Scala 3 project is to use one of the following templates: 17 | 18 | * [Minimal Scala 3 project](https://github.com/scala/scala3.g8) 19 | * [Scala 3 project that cross-compiles with Scala 2](https://github.com/scala/scala3-cross.g8) 20 | 21 | ## Using Scala 3 in an existing project 22 | 23 | You will need to make the following adjustments to your build: 24 | 25 | ### project/build.properties 26 | 27 | ``` 28 | sbt.version=1.11.0 29 | ``` 30 | 31 | You must use sbt 1.5.5 or newer; older versions of sbt are not supported. 32 | 33 | ### build.sbt 34 | 35 | Set the Scala 3 version: 36 | 37 | ```scala 38 | scalaVersion := "3.7.0" 39 | ``` 40 | 41 | ### Getting your project to compile with Scala 3 42 | 43 | For help with porting an existing Scala 2 project to Scala 3, see the 44 | [Scala 3 migration guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html). 45 | 46 | ## Need help? 47 | 48 | https://www.scala-lang.org/community/ has links. 49 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | lazy val root = project 2 | .in(file(".")) 3 | .settings( 4 | name := "scala3-example-project", 5 | description := "Example sbt project that compiles using Scala 3", 6 | version := "0.1.0", 7 | scalaVersion := "3.7.0", 8 | scalacOptions ++= Seq("-deprecation"), 9 | libraryDependencies += "org.scalameta" %% "munit" % "1.1.1" % Test 10 | ) 11 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.0 2 | -------------------------------------------------------------------------------- /src/main/scala/ContextFunctions.scala: -------------------------------------------------------------------------------- 1 | import scala.concurrent.{ExecutionContext, Future} 2 | import scala.util.Try 3 | 4 | /** 5 | * Context Functions: 6 | * - https://dotty.epfl.ch/docs/reference/contextual/context-functions.html 7 | * - https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html 8 | */ 9 | object ContextFunctions: 10 | 11 | object context: 12 | // type alias Contextual 13 | type Contextual[T] = ExecutionContext ?=> T 14 | 15 | // sum is expanded to sum(x, y)(ctx) 16 | def asyncSum(x: Int, y: Int): Contextual[Future[Int]] = Future(x + y) 17 | 18 | def asyncMult(x: Int, y: Int)(using ctx: ExecutionContext) = Future(x * y) 19 | 20 | object parse: 21 | 22 | type Parseable[T] = GivenInstances.StringParser[T] ?=> Try[T] 23 | 24 | def sumStrings(x: String, y: String): Parseable[Int] = 25 | val parser = summon[GivenInstances.StringParser[Int]] 26 | val tryA = parser.parse(x) 27 | val tryB = parser.parse(y) 28 | 29 | for 30 | a <- tryA 31 | b <- tryB 32 | yield a + b 33 | 34 | def test(): Unit = 35 | import ExecutionContext.Implicits.global 36 | context.asyncSum(3, 4).foreach(println) 37 | context.asyncMult(3, 4).foreach(println) 38 | 39 | println(parse.sumStrings("3", "4")) 40 | println(parse.sumStrings("3", "a")) 41 | 42 | -------------------------------------------------------------------------------- /src/main/scala/Conversion.scala: -------------------------------------------------------------------------------- 1 | import scala.language.implicitConversions 2 | 3 | /** 4 | * Conversions: https://dotty.epfl.ch/docs/reference/contextual/conversions.html 5 | */ 6 | object Conversion: 7 | 8 | case class IntWrapper(a: Int) extends AnyVal 9 | case class DoubleWrapper(b: Double) extends AnyVal 10 | 11 | def convert[T, U](x: T)(using converter: Conversion[T, U]): U = converter(x) 12 | 13 | given IntWrapperToDoubleWrapper: Conversion[IntWrapper, DoubleWrapper] = new Conversion[IntWrapper, DoubleWrapper] { 14 | override def apply(i: IntWrapper): DoubleWrapper = DoubleWrapper(i.a.toDouble) 15 | } 16 | // Or: 17 | // given IntWrapperToDoubleWrapper: Conversion[IntWrapper, DoubleWrapper] = 18 | // (i: IntWrapper) => DoubleWrapper(i.a.toDouble) 19 | 20 | def useConversion(using f: Conversion[IntWrapper, DoubleWrapper]) = 21 | val y: IntWrapper = IntWrapper(4) 22 | val x: DoubleWrapper = y 23 | x 24 | 25 | /* Not working anymore. 26 | def useConversion(implicit f: A => B) = { 27 | val y: A = ... 28 | val x: B = a // error under Scala 3 29 | } 30 | */ 31 | 32 | def test(): Unit = 33 | println(useConversion) 34 | println(convert(IntWrapper(42))) 35 | -------------------------------------------------------------------------------- /src/main/scala/EnumTypes.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Enum Types: https://dotty.epfl.ch/docs/reference/enums/adts.html 3 | */ 4 | object EnumTypes: 5 | 6 | enum ListEnum[+A]: 7 | case Cons(h: A, t: ListEnum[A]) 8 | case Empty 9 | 10 | 11 | enum Planet(mass: Double, radius: Double): 12 | private final val G = 6.67300E-11 13 | def surfaceGravity = G * mass / (radius * radius) 14 | def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity 15 | 16 | case Mercury extends Planet(3.303e+23, 2.4397e6) 17 | case Venus extends Planet(4.869e+24, 6.0518e6) 18 | case Earth extends Planet(5.976e+24, 6.37814e6) 19 | case Mars extends Planet(6.421e+23, 3.3972e6) 20 | case Jupiter extends Planet(1.9e+27, 7.1492e7) 21 | case Saturn extends Planet(5.688e+26, 6.0268e7) 22 | case Uranus extends Planet(8.686e+25, 2.5559e7) 23 | case Neptune extends Planet(1.024e+26, 2.4746e7) 24 | end Planet 25 | 26 | def test(): Unit = 27 | val emptyList = ListEnum.Empty 28 | val list = ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) 29 | println("Example 1: \n"+emptyList) 30 | println(s"${list}\n") 31 | 32 | def calculateEarthWeightOnPlanets(earthWeight: Double) = 33 | val mass = earthWeight/Planet.Earth.surfaceGravity 34 | for p <- Planet.values do 35 | println(s"Your weight on $p is ${p.surfaceWeight(mass)}") 36 | 37 | println("Example 2:") 38 | calculateEarthWeightOnPlanets(80) 39 | end test 40 | -------------------------------------------------------------------------------- /src/main/scala/GivenInstances.scala: -------------------------------------------------------------------------------- 1 | import scala.util.{Success, Try} 2 | 3 | /** 4 | * Implied Instances: https://dotty.epfl.ch/docs/reference/contextual/givens.html 5 | */ 6 | object GivenInstances: 7 | 8 | sealed trait StringParser[A]: 9 | def parse(s: String): Try[A] 10 | 11 | object StringParser: 12 | 13 | def apply[A](using parser: StringParser[A]): StringParser[A] = parser 14 | 15 | private def baseParser[A](f: String => Try[A]): StringParser[A] = new StringParser[A] { 16 | override def parse(s: String): Try[A] = f(s) 17 | } 18 | 19 | given stringParser: StringParser[String] = baseParser(Success(_)) 20 | given intParser: StringParser[Int] = baseParser(s => Try(s.toInt)) 21 | 22 | given optionParser[A](using parser: => StringParser[A]): StringParser[Option[A]] = new StringParser[Option[A]] { 23 | override def parse(s: String): Try[Option[A]] = s match 24 | case "" => Success(None) // implicit parser not used. 25 | case str => parser.parse(str).map(x => Some(x)) // implicit parser is evaluated at here 26 | } 27 | 28 | def test(): Unit = 29 | println(summon[StringParser[Option[Int]]].parse("21")) 30 | println(summon[StringParser[Option[Int]]].parse("")) 31 | println(summon[StringParser[Option[Int]]].parse("21a")) 32 | println(summon[StringParser[Option[Int]]](using StringParser.optionParser[Int]).parse("42")) 33 | 34 | -------------------------------------------------------------------------------- /src/main/scala/IntersectionTypes.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Intersection Types: https://dotty.epfl.ch/docs/reference/new-types/intersection-types.html 3 | */ 4 | object IntersectionTypes: 5 | 6 | sealed trait X: 7 | def x: Double 8 | def tpe: X 9 | 10 | sealed trait Y: 11 | def y: Double 12 | def tpe: Y 13 | 14 | type P = Y & X 15 | type PP = X & Y 16 | 17 | final case class Point(x: Double, y: Double) extends X with Y: 18 | override def tpe: X & Y = ??? 19 | 20 | def test(): Unit = 21 | def euclideanDistance(p1: X & Y, p2: X & Y) = 22 | Math.sqrt(Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2)) 23 | 24 | val p1: P = Point(3, 4) 25 | val p2: PP = Point(6, 8) 26 | println(euclideanDistance(p1, p2)) 27 | 28 | -------------------------------------------------------------------------------- /src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | @main 2 | def Main(args: String*): Unit = 3 | runExample("Trait Params")(TraitParams.test()) 4 | 5 | runExample("Enum Types")(EnumTypes.test()) 6 | 7 | runExample("Context Functions")(ContextFunctions.test()) 8 | 9 | runExample("Given Instances")(GivenInstances.test()) 10 | 11 | runExample("Conversion")(Conversion.test()) 12 | 13 | runExample("Union Types")(UnionTypes.test()) 14 | 15 | runExample("Intersection Types")(IntersectionTypes.test()) 16 | 17 | runExample("Type Lambda")(TypeLambdas.test()) 18 | 19 | runExample("Multiversal Equality")(MultiversalEquality.test()) 20 | 21 | runExample("Parameter Untupling")(ParameterUntupling.test()) 22 | 23 | runExample("Structural Types")(StructuralTypes.test()) 24 | 25 | runExample("Pattern Matching")(PatternMatching.test()) 26 | end Main 27 | 28 | private def runExample(name: String)(f: => Unit): Unit = 29 | println(Console.MAGENTA + s"$name example:" + Console.RESET) 30 | f 31 | println() 32 | -------------------------------------------------------------------------------- /src/main/scala/MultiversalEquality.scala: -------------------------------------------------------------------------------- 1 | import scala.language.strictEquality 2 | 3 | /** 4 | * Multiversal Equality: https://dotty.epfl.ch/docs/reference/contextual/multiversal-equality.html 5 | * scala.CanEqual definition: https://github.com/lampepfl/dotty/blob/master/library/src/scala/CanEqual.scala 6 | */ 7 | object MultiversalEquality: 8 | 9 | def test(): Unit = 10 | // Values of types Int and String cannot be compared with == or !=, 11 | // unless we add the derived delegate instance like: 12 | given CanEqual[Int, String] = CanEqual.derived 13 | println(3 == "3") 14 | 15 | // By default, all numbers are comparable, because of; 16 | // given canEqualNumber as CanEqual[Number, Number] = derived 17 | println(3 == 5.1) 18 | 19 | // By default, all Sequences are comparable, because of; 20 | // given canEqualSeq[T, U](using eq: CanEqual[T, U]) as CanEqual[Seq[T], Seq[U]] = derived 21 | println(List(1, 2) == Vector(1, 2)) 22 | 23 | class A(a: Int) 24 | class B(b: Int) 25 | 26 | val a = A(4) 27 | val b = B(4) 28 | 29 | // scala.language.strictEquality is enabled, therefore we need some extra delegate instances 30 | // to compare instances of A and B. 31 | given CanEqual[A, B] = CanEqual.derived 32 | given CanEqual[B, A] = CanEqual.derived 33 | 34 | println(a != b) 35 | println(b == a) 36 | 37 | -------------------------------------------------------------------------------- /src/main/scala/ParameterUntupling.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Parameter Untupling: https://dotty.epfl.ch/docs/reference/other-new-features/parameter-untupling.html 3 | */ 4 | object ParameterUntupling: 5 | 6 | def test(): Unit = 7 | val xs: List[String] = List("d", "o", "t", "t", "y") 8 | 9 | /** 10 | * Current behaviour in Scala 2.12.2 : 11 | * error: missing parameter type 12 | * Note: The expected type requires a one-argument function accepting a 2-Tuple. 13 | * Consider a pattern matching anonymous function, `{ case (s, i) => ... }` 14 | */ 15 | xs.zipWithIndex.map((s, i) => println(s"$i: $s")) 16 | 17 | -------------------------------------------------------------------------------- /src/main/scala/PatternMatching.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Pattern Matching: https://dotty.epfl.ch/docs/reference/changed-features/pattern-matching.html 3 | */ 4 | object PatternMatching: 5 | 6 | object booleanPattern: 7 | 8 | object Even: 9 | def unapply(s: String): Boolean = s.length % 2 == 0 10 | 11 | 12 | object productPattern: 13 | 14 | class Person(name: String, age: Int) extends Product: 15 | // if we not define that, it will give compile error. 16 | // we change the order 17 | def _1 = age 18 | def _2 = name 19 | 20 | // Not used by pattern matching: Product is only used as a marker trait. 21 | def canEqual(that: Any): Boolean = ??? 22 | def productArity: Int = ??? 23 | def productElement(n: Int): Any = ??? 24 | 25 | object Person: 26 | def unapply(a: (String, Int)): Person = Person(a._1, a._2) 27 | 28 | 29 | object seqPattern: 30 | 31 | // adapted from https://danielwestheide.com/blog/the-neophytes-guide-to-scala-part-2-extracting-sequences/ 32 | object Names: 33 | def unapplySeq(name: String): Option[Seq[String]] = 34 | val names = name.trim.split(" ") 35 | if names.size < 2 then None 36 | else Some(names.last :: names.head :: names.drop(1).dropRight(1).toList) 37 | 38 | 39 | object namePattern: 40 | 41 | class Name(val name: String): 42 | def get: String = name 43 | def isEmpty = name.isEmpty 44 | 45 | object Name: 46 | def unapply(s: String): Name = Name(s) 47 | 48 | 49 | def test(): Unit = 50 | import booleanPattern.* 51 | 52 | "even" match 53 | case s @ Even() => println(s"$s has an even number of characters") 54 | case s => println(s"$s has an odd number of characters") 55 | 56 | // https://dotty.epfl.ch/docs/reference/changed-features/vararg-splices.html 57 | def containsConsecutive(list: List[Int]): Boolean = list match 58 | case List(a, b, xs*) => a == b || containsConsecutive(b :: xs.toList) 59 | case Nil | List(_, _*) => false 60 | 61 | println(containsConsecutive(List(1, 2, 3, 4, 5))) 62 | println(containsConsecutive(List(1, 2, 3, 3, 5))) 63 | 64 | import productPattern.* 65 | ("john", 42) match 66 | case Person(n, a) => println(s"name: $n, age: $a") 67 | 68 | import seqPattern.* 69 | 70 | def greet(fullName: String) = fullName match 71 | case Names(lastName, firstName, _*) => "Good morning, " + firstName + " " + lastName + "!" 72 | case _ => "Welcome! Please make sure to fill in your name!" 73 | 74 | println(greet("Alan Turing")) 75 | println(greet("john")) 76 | println(greet("Wolfgang Amadeus Mozart")) 77 | 78 | import namePattern.* 79 | "alice" match 80 | case Name(n) => println(s"name is $n") 81 | case _ => println("empty name") 82 | 83 | end PatternMatching -------------------------------------------------------------------------------- /src/main/scala/StructuralTypes.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Structural Types: https://dotty.epfl.ch/docs/reference/changed-features/structural-types.html 3 | */ 4 | object StructuralTypes: 5 | 6 | case class Record(elems: (String, Any)*) extends Selectable: 7 | def selectDynamic(name: String): Any = elems.find(_._1 == name).get._2 8 | 9 | type Person = Record { 10 | val name: String 11 | val age: Int 12 | } 13 | 14 | val person = Record("name" -> "Emma", "age" -> 42, "salary" -> 320L).asInstanceOf[Person] 15 | 16 | val invalidPerson = Record("name" -> "John", "salary" -> 42).asInstanceOf[Person] 17 | 18 | def test(): Unit = 19 | println(person.name) 20 | println(person.age) 21 | 22 | println(invalidPerson.name) 23 | // age field is java.util.NoSuchElementException: None.get 24 | //println(invalidPerson.age) 25 | 26 | end StructuralTypes -------------------------------------------------------------------------------- /src/main/scala/TraitParams.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Trait Parameters: https://dotty.epfl.ch/docs/reference/other-new-features/trait-parameters.html 3 | */ 4 | object TraitParams: 5 | 6 | trait Base(val msg: String) 7 | class A extends Base("Hello") 8 | class B extends Base("Dotty!") 9 | 10 | // Union types only exist in Scala 3, so there's no chance that this will accidentally be compiled with Scala 2 11 | private def printMessages(msgs: (A | B)*) = println(msgs.map(_.msg).mkString(" ")) 12 | 13 | def test(): Unit = 14 | printMessages(new A, new B) 15 | 16 | // Sanity check the classpath: this won't run if the Scala 3 jar is not present. 17 | val x: Int => Int = identity 18 | x(1) 19 | 20 | -------------------------------------------------------------------------------- /src/main/scala/TypeLambdas.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Type Lambdas: https://dotty.epfl.ch/docs/reference/new-types/type-lambdas.html 3 | */ 4 | object TypeLambdas: 5 | 6 | type M = [X, Y] =>> Map[Y, X] 7 | 8 | type Tuple = [X] =>> (X, X) 9 | 10 | def test(): Unit = 11 | val m: M[String, Int] = Map(1 -> "1") 12 | println(m) 13 | 14 | val tuple: Tuple[String] = ("a", "b") 15 | println(tuple) 16 | 17 | -------------------------------------------------------------------------------- /src/main/scala/UnionTypes.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Union Types: https://dotty.epfl.ch/docs/reference/new-types/union-types.html 3 | */ 4 | object UnionTypes: 5 | 6 | sealed trait Division 7 | final case class DivisionByZero(msg: String) extends Division 8 | final case class Success(double: Double) extends Division 9 | 10 | // You can create type aliases for your union types (sum types). 11 | type DivisionResult = DivisionByZero | Success 12 | 13 | sealed trait List[+A] 14 | case object Empty extends List[Nothing] 15 | final case class Cons[+A](h: A, t: List[A]) extends List[A] 16 | 17 | private def safeDivide(a: Double, b: Double): DivisionResult = 18 | if b == 0 then DivisionByZero("DivisionByZeroException") else Success(a / b) 19 | 20 | private def either(division: Division) = division match 21 | case DivisionByZero(m) => Left(m) 22 | case Success(d) => Right(d) 23 | 24 | def test(): Unit = 25 | val divisionResultSuccess: DivisionResult = safeDivide(4, 2) 26 | 27 | // commutative 28 | val divisionResultFailure: Success | DivisionByZero = safeDivide(4, 0) 29 | 30 | // calling `either` function with union typed value. 31 | println(either(divisionResultSuccess)) 32 | 33 | // calling `either` function with union typed value. 34 | println(either(divisionResultFailure)) 35 | 36 | val list: Cons[Int] | Empty.type = Cons(1, Cons(2, Cons(3, Empty))) 37 | val emptyList: Empty.type | Cons[Any] = Empty 38 | println(list) 39 | println(emptyList) 40 | 41 | -------------------------------------------------------------------------------- /src/test/scala/MySuite.scala: -------------------------------------------------------------------------------- 1 | // For more information on writing tests, see 2 | // https://scalameta.org/munit/docs/getting-started.html 3 | class MySuite extends munit.FunSuite { 4 | test("example test that succeeds") { 5 | val obtained = 42 6 | val expected = 42 7 | assertEquals(obtained, expected) 8 | } 9 | } 10 | --------------------------------------------------------------------------------