From ea13ed4cb9e00ca9611a006fd304eb1e219d92f0 Mon Sep 17 00:00:00 2001 From: Eric Loots Date: Wed, 6 Sep 2023 09:16:03 +0200 Subject: [PATCH 1/9] Update recap chapter --- slides/02-recap-scala2-akka-typed-scala3.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/slides/02-recap-scala2-akka-typed-scala3.md b/slides/02-recap-scala2-akka-typed-scala3.md index 9ee56e191..d8373a59d 100644 --- a/slides/02-recap-scala2-akka-typed-scala3.md +++ b/slides/02-recap-scala2-akka-typed-scala3.md @@ -18,7 +18,7 @@ * 2.13 * Important progress on compiler performance and collections rewrite * New, binary compatible *`Vector`* implementation - * Most recent release 2.13.10 + * Most recent release 2.13.11 * Allow, under some restrictions to use Scala 3 libraries in Scala 2 code (using Tasty) and vice-versa. See the [Compatibility Reference pages](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) in the Scala 3 Migration Guide --- @@ -28,12 +28,12 @@ * Dotty 0.21.0-RC1: feature complete for Scala 3 (December 2019) * 3.0.0 released on May 13, 2021 -* Current release 3.2.2 released on February 1st, 2023 -* Tooling - Status - * Metals: +* Current [release 3.3.0](https://github.com/lampepfl/dotty/releases/tag/3.3.0) released on May 30th, 2023 +* IDE support + * [Visual Studio Code](https://code.visualstudio.com) with [Metals](https://scalameta.org/metals/) * integrated support in Metals including Scala Worksheet support! - * IntelliJ - * Scala 3 support in Scala Plugin + * [IntelliJ](https://www.jetbrains.com/idea/) + * Scala 3 support with the [Scala Plugin](https://lp.jetbrains.com/intellij-scala/) * There's the [Scala 3 Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html)! --- From cab2ae4d9506a559a60c1a2ed764a41cfb0aad05 Mon Sep 17 00:00:00 2001 From: Eric Loots Date: Wed, 6 Sep 2023 11:02:41 +0200 Subject: [PATCH 2/9] Update chapter of the Scala compiler's rewriting capabilities --- slides/04-scala3-compiler-rewrite-capabilities.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/slides/04-scala3-compiler-rewrite-capabilities.md b/slides/04-scala3-compiler-rewrite-capabilities.md index 2d5c568e1..4f96af915 100644 --- a/slides/04-scala3-compiler-rewrite-capabilities.md +++ b/slides/04-scala3-compiler-rewrite-capabilities.md @@ -32,7 +32,7 @@ --- -## Rewriting of deprecated syntax - I +## Rewriting of deprecated syntax - II * The Scala 3 compiler has a number of options that can automatically rewrite some deprecated language features * There's a strategy defining how language features will be phased in and out after the Scala 3.0 release * Scala 3.0 @@ -41,6 +41,8 @@ * Scala 3.x (x >= 1) * Support of Scala 3.0 syntax which introduced new deprecations * Rewrite of these via the ***-rewrite -source:future-migration*** compile options +* Use the [***Scala 3 Migrate sbt plugin***](https://github.com/scalacenter/scala3-migrate) + * Learn how to use it by following the [***Scala 3 Migration Course***](https://github.com/scalacenter/scala3-migration-course) --- @@ -57,12 +59,12 @@ ## ­ * Scala 3 introduces a number of syntax changes for Scala 2 language constructs such as - * Contextual Abstractions (implicits) - * Extension methods + * [***Contextual Abstractions***](https://dotty.epfl.ch/docs/reference/contextual/index.html) (implicits) + * [***Extension methods***](https://dotty.epfl.ch/docs/reference/contextual/extension-methods.html) * ... * Two other notable syntax changes - * New Control Syntax (opt-in) - * "Fewer Braces" syntax: indentation is significant + * [***New Control Syntax***](https://dotty.epfl.ch/docs/reference/other-new-features/control-syntax.html) (opt-in) + * [***Optional Braces syntax***](https://dotty.epfl.ch/docs/reference/other-new-features/indentation.html) - indentation is significant --- From 05e5d1998f70f782b4bcf31092817dfde56101f8 Mon Sep 17 00:00:00 2001 From: Eric Loots Date: Wed, 6 Sep 2023 11:03:09 +0200 Subject: [PATCH 3/9] Update chapter on Top Level Definitions --- slides/05-top-level-definitions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slides/05-top-level-definitions.md b/slides/05-top-level-definitions.md index 4cb4cc66b..aceeeb0f5 100644 --- a/slides/05-top-level-definitions.md +++ b/slides/05-top-level-definitions.md @@ -12,7 +12,7 @@ ## Scala 3 Top-level definitions * In Scala 2: - * The source code for a package object is usually put in a separate file called ***`package.scala`*** + * The source code for a package object is put in a separate file named ***`package.scala`*** * Each package is allowed to have only one package object * Any definitions placed inside a package object are considered members of the package itself * ***`type`***, ***`def`***, and ***`val`*** definitions have to be put in an ***`object`***, ***`class`***, or ***`trait`*** @@ -85,7 +85,7 @@ def greetPerson(person: Person): Unit = * The compiler generates synthetic objects to wrap top-level definitions * In the above example, the contents of the source file ***`ToplevelDefinitions.scala`*** will be put in a synthetic object named ***`ToplevelDefinitions$package `*** -* *Top-level definitions don't have the ability to inherit from another ***`trait`*** or ***`class`*** as we could do with ***`package object`***­*s* +* Top-level definitions don't have the ability to inherit from another ***`trait`*** or ***`class`*** as we could do with ***`package object`***­*s* * *A workaround for this is to use a regular object and import all needed members* --- @@ -116,7 +116,7 @@ private val private_y = 730 package org.lunatechlabs.multi object Application extends App { - println(s"$x $y $private_x $private_y”) + println(s"$x $y $private_x $private_y") } ``` @@ -126,7 +126,7 @@ object Application extends App { ## ­ * In this chapter, we have taken a closer look at top-level definitions. - * Package objects are now redundant and will be removed from Scala. + * Package objects are now redundant and will be removed from Scala at some point in time * A source file can freely mix top-level * value * methods From 82dc6e7406f441d8cde2cfe1ac571514c9239b9d Mon Sep 17 00:00:00 2001 From: Eric Loots Date: Wed, 6 Sep 2023 11:03:27 +0200 Subject: [PATCH 4/9] Update chapter on Extension methods --- slides/07-extension-methods.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/slides/07-extension-methods.md b/slides/07-extension-methods.md index e2a426e9d..27dd28fd4 100644 --- a/slides/07-extension-methods.md +++ b/slides/07-extension-methods.md @@ -14,12 +14,14 @@ ## ­ * Scala's *implicits* enable abstraction over context +### ­ * *implicits* enables overcoming common phenomena in Functional Programming: * Writing out complex instances in full can be very cumbersome * Functions lead to long argument lists, which render code less readable and feels like boilerplate * The killer application for *implicits* are so-called Type classes * Note that using *implicits* to pass in parameters implicitly is powerful but can also be quite confusing. - +### ­ +* Caveat: *implicits* may make things a bit to easy to ignore. A good example of this is the availability of a global execution context in the Scala Runtime environment --- ## Contextual Abstractions - II @@ -30,7 +32,7 @@ * Adding a separate so-called *implicit parameter* list to a function * When such a function is called, the compiler will look-up the *implicit value *in scope (applying various scoping rules) and pass it to the function * In case no *implicit value* is found, the code will not compile -## ­ +### ­ * Instead of typing out complex instances in full, contextual abstractions allow one to *"summon"* such instances based on their type --- @@ -188,7 +190,7 @@ case (Some(d), Some(r)) => Some((d, r)) case _ => None - import SafeDiv.*; import SafeDiv.* + import SafeDiv.* val d1 = 25.divide(3) // Some((8,1)) val d2 = 25.divide(0) // None @@ -263,4 +265,4 @@ * In this exercise we will use extension methods instead of implicit classes to add methods to our types. * Make sure you're positioned at exercise *"extension methods"* - * Follow the exercise instructions provided in the README.md file in the code folder \ No newline at end of file + * Follow the exercise instructions provided in the README.md file in the code folder From f302cf17edba045486e3502d088ed02a2a1ed9f1 Mon Sep 17 00:00:00 2001 From: Eric Loots Date: Wed, 6 Sep 2023 13:11:14 +0200 Subject: [PATCH 5/9] Update chapter on given/using/summon --- slides/08-given-using-summon.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/slides/08-given-using-summon.md b/slides/08-given-using-summon.md index b1717f323..70d579aad 100644 --- a/slides/08-given-using-summon.md +++ b/slides/08-given-using-summon.md @@ -15,11 +15,14 @@ * In the chapters on ***`extension method`***­s, we mentioned abstraction over context -* via ***`implicit`***­s in Scala 2 -* via ***given*** and ***`using`*** in Scala 3 + * via ***`implicit`***­s in Scala 2 + * via ***`given`*** and ***`using`*** in Scala 3 +#### ­ * ***`given`*** instances define a *"canonical"* value of a certain type that can be used for synthesising arguments for context parameters (***`using`*** clauses) -* The introduction of the new keywords given and using allow us to make a clear distinction between the definition of *"canonical"* values and the actual use of them +#### ­ +* The introduction of the new keywords ***`given`*** and ***`using`*** allows us to make a clear distinction between the definition of *"canonical"* values and the actual use of them * This eliminates the confusion that was caused by the multiple distinctive use cases of the Scala 2 ***`implicit`*** keyword +#### ­ * When moving from Scala 2, we have the benefit to move to Scala 3's *Contextual Abstractions* in steps --- @@ -97,9 +100,9 @@ startEngine(Engine("AC-35-B/002")) ``` * The synthesised type names use the following rules: -* The prefix ***given_*** -* The simple name(s) of the implemented type(s) -* The simple name(s) of the toplevel argument type constructors to these types + * The prefix ***given_*** + * The simple name(s) of the implemented type(s) + * The simple name(s) of the toplevel argument type constructors to these types --- @@ -117,6 +120,7 @@ def startEngine(engine: Engine)(using config: EngineConfig): Unit = * We have: * A [single] regular parameter list with a single parameter * A [single] ***`using`*** clause with a single context parameter +#### ­ * Scala 3 allows for more than one using clause. Let's have a look at this --- @@ -237,7 +241,7 @@ val res5: Int = 3 ``` * Context bounds in Scala 3 maps to old-style implicit parameters in Scala 3.0 to ease the migration. -* From Scala 3.1 onwards, they will map to context clauses instead. +* From Scala 3.1 onwards, they will map to using clauses instead. * It means, the following will still be allowed in Scala 3.0, but generate a warning in later versions ```scala From 6b6735e143dad4605152550f33a683d4824cdae1 Mon Sep 17 00:00:00 2001 From: Eric Loots Date: Wed, 6 Sep 2023 15:25:55 +0200 Subject: [PATCH 6/9] Update chapter on enumerations and export --- slides/09-enumerations-and-export.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/slides/09-enumerations-and-export.md b/slides/09-enumerations-and-export.md index f25e4a3dc..29889f69d 100644 --- a/slides/09-enumerations-and-export.md +++ b/slides/09-enumerations-and-export.md @@ -14,13 +14,15 @@ * Scala 2 has ***`Enumerations`*** * Their implementation is awkward for even simple use-cases such as defining a finite number of user-defined elements -### ­ +#### ­ * Scala 3 adds the new and versatile ***`enum`*** construct which is syntactic sugar +#### ­ * The design of ***`enum`*** meets the following objectives: * Allow for a concise expression and efficient definition of enumerations * Allow the modelling of Java enumerations as Scala enumerations * Allow for a concise expression of ADTs * Support of all idioms expressible with case classes +#### ­ * Let's have a short look at each of them --- @@ -98,7 +100,8 @@ enum Planet(mass: Double, radius: Double): * Defining an explicit companion object for an enum ```scala -object Planet: +object Planets: + import Planet.* val earthWeight = 1.0 val mass = earthWeight / Earth.surfaceGravity val weightOnPlanets = @@ -204,7 +207,7 @@ scala> val reset = Command.Reset // `reset` is a singleton case mapped t scala> println(r1 eq r2) true scala> inc. // which members are defined on this enum instance? -→ -> ne equals $ordinal getClass formatted isInstanceOf + -> ne equals $ordinal getClass formatted isInstanceOf != == wait notify ensuring hashCode notifyAll synchronized ## eq clone ordinal finalize toString asInstanceOf ``` @@ -225,7 +228,7 @@ scala> enum Command: scala> val inc = new Command.IncrementBy(2) // `inc` IS a case class val conf = new Command.Configure(0, 5) // `conf` IS a case class -scala> println(s"inc.ordinal = ${inc.ordinal}”) +scala> println(s"inc.ordinal = ${inc.ordinal}") inc.ordinal = 1 scala> val conf1 = conf.copy(inc = 7) @@ -233,12 +236,12 @@ scala> val conf1 = conf.copy(inc = 7) Configure(0,7) scala> conf1. // which members are defined on this enum instance? -→ _2 wait canEqual formatted productPrefix -!= eq clone ensuring notifyAll productElement -## ne equals finalize asInstanceOf productIterator --> inc notify getClass isInstanceOf productElementName -== copy ordinal hashCode productArity productElementNames -_1 init $ordinal toString synchronized + _2 wait canEqual formatted productPrefix +!= eq clone ensuring notifyAll productElement +## ne equals finalize asInstanceOf productIterator +-> inc notify getClass isInstanceOf productElementName +== copy ordinal hashCode productArity productElementNames +_1 init $ordinal toString synchronized ``` --- @@ -246,7 +249,7 @@ _1 init $ordinal toString ## Scala 3's ***`export`*** clause - I ## ­ -* Scala 3's export clause allows us to create alias of selected members +* Scala 3's export clause allows us to create aliases for selected members ```scala object A: @@ -263,7 +266,7 @@ scala> A.x val res1: Int = 5 ``` -* We have only scratched the surface of the ***`export`*** clause. It enables us to compose new functionality rather than going through OOP's inheritance based approach. More details on this in the ***`export`*** [reference documentation](https://dotty.epfl.ch/docs/reference/other-new-features/export.html) +* We have only scratched the surface of the ***`export`*** clause. It enables us to compose new functionality rather than going through OOP's inheritance based approach. More details on this in the ***`export`*** [***reference documentation***](https://dotty.epfl.ch/docs/reference/other-new-features/export.html) --- From 1aadac4c9c63232a32faa590ad63901501d4970d Mon Sep 17 00:00:00 2001 From: Eric Loots Date: Wed, 6 Sep 2023 15:26:25 +0200 Subject: [PATCH 7/9] Update chapter on intersection- and union types --- slides/10-intersection-and-union-types.md | 27 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/slides/10-intersection-and-union-types.md b/slides/10-intersection-and-union-types.md index ae87cab5f..2695281ec 100644 --- a/slides/10-intersection-and-union-types.md +++ b/slides/10-intersection-and-union-types.md @@ -59,8 +59,17 @@ trait Paintable: def resizeAndPaint(obj: Growable & Paintable): Unit = obj.growBy(20).paint(0x10FF00).growBy(40).paint(0x0010FF) + +``` + +* and using it: -resizeAndPaint(new Growable with Paintable) +```scala +scala> resizeAndPaint(new Growable with Paintable) + Growing by 20% + Painted with color 1113856 + Growing by 40% + Painted with color 4351 ``` --- @@ -103,6 +112,7 @@ enum ToolSupplies: def printIt(t: Tools | ToolSupplies): Unit = t match case tool: Tools => println(s"Got a tool: $tool") case supply: ToolSupplies => println(s"Got a supply: $supply") + ``` * and using it: @@ -124,9 +134,11 @@ Got a supply: Nail(9) ## ­ * The behaviour of an Actor is implemented via the ***`akka.actor.typed.Behavior`*** API +#### ­ * The ***`Behavior`*** takes a type parameter which corresponds to the message types the ***`Behavior`*** can and will handle as formally defined in its ***`Command`*** protocol +#### ­ * On top of the commands an Actor can process, it will also have to process Responses from other Actors as defined in the latter's ***`Response`*** protocol -### ­ +#### ­ * How do we encode a behaviour so that it can process both commands and responses internally, while limiting the external protocol to ***`Command`***? --- @@ -181,10 +193,11 @@ Got a supply: Nail(9) ### Akka Typed Actors - encoding * Message adapters with Response wrappers solve the issue, but - * this adds quite a bit of boilerplate + * this is quite convoluted and it adds a lot of boilerplate * requires an extra effort from anyone trying to understand the code -### ­ +#### ­ * There is a better solution using Scala 3's ***`Union`*** Types! +#### ­ * Let's explore that --- @@ -211,6 +224,7 @@ type CommandAndResponse = Command | Response // implicitly[Behavior[CommandAndResponse] <:< Behavior[Command]] val internalBehavior: Behavior[CommandAndResponse] = new Behavior[CommandAndResponse]{} val externalBehavior: Behavior[Command] = internalBehavior // Contravariance at work + ``` ```scala internalBehavior.treatMsg(Command.Reset) @@ -220,7 +234,8 @@ internalBehavior.treatMsg(Response.RunFinished) externalBehavior.treatMsg(Command.Reset) externalBehavior.treatMsg(Command.Run(110)) -externalBehavior.treatMsg(Response.RunFailed("Too much to do”)) // Doesn’t compile +externalBehavior.treatMsg(Response.RunFailed("Too much to do")) // Doesn't compile + ``` --- @@ -244,4 +259,4 @@ externalBehavior.treatMsg(Response.RunFailed("Too much to do”)) // Doesn’t * In this exercise, we will utilise ***`Union Types`*** to vastly simplify the handling of responses to messages sent by an Akka actor * Make sure you're positioned at exercise *"union types"* - * Follow the exercise instructions provided in the README.md file in the code folder \ No newline at end of file + * Follow the exercise instructions provided in the README.md file in the code folder From 7714bfea7db473e61daa9ff739657e4e85a618ec Mon Sep 17 00:00:00 2001 From: Eric Loots Date: Wed, 6 Sep 2023 15:26:45 +0200 Subject: [PATCH 8/9] Update chapter on opaque type aliases --- slides/11-opaque-type-aliases.md | 210 ++++++++++++++++--------------- 1 file changed, 108 insertions(+), 102 deletions(-) diff --git a/slides/11-opaque-type-aliases.md b/slides/11-opaque-type-aliases.md index 581d45f49..2b79de28f 100644 --- a/slides/11-opaque-type-aliases.md +++ b/slides/11-opaque-type-aliases.md @@ -113,73 +113,77 @@ --- -## Value-class wrappers limitations +## Value-class wrappers limitations - I ## ­ * BUT! Allocations happen in many cases (e.g. parametric polymorphism) ```scala - case class Kilometres(value: Double) extends AnyVal - case class Miles(value: Double) extends AnyVal - - class Rocket(distanceTravelled: Kilometres): - def advance(distanceToAdvance: Kilometres): Rocket = new Rocket( - Kilometres(distanceTravelled.value + distanceToAdvance.value) - ) - - type Conversion[A] = A => Kilometres - class Booster(): - def advanceRocket[A: Conversion](rocket: Rocket, distanceToAdvance: A): Rocket = { - val distanceInKm = summon[Conversion[A]](distanceToAdvance) - rocket.advance(distanceInKm) - } +case class Kilometres(value: Double) extends AnyVal +case class Miles(value: Double) extends AnyVal + +class Rocket(distanceTravelled: Kilometres): + def advance(distanceToAdvance: Kilometres): Rocket = new Rocket( + Kilometres(distanceTravelled.value + distanceToAdvance.value) + ) + +type Conversion[A] = A => Kilometres +class Booster(): + def advanceRocket[A: Conversion](rocket: Rocket, distanceToAdvance: A): Rocket = { + val distanceInKm = summon[Conversion[A]](distanceToAdvance) + rocket.advance(distanceInKm) + } + ``` ```scala - val rocket1 = new Rocket(Kilometres(0)) - val rocket2 = new Rocket(Kilometres(0)) - val booster = new Booster() +val rocket1 = new Rocket(Kilometres(0)) +val rocket2 = new Rocket(Kilometres(0)) +val booster = new Booster() - given Conversion[Kilometres] = identity - given Conversion[Miles] = miles => Kilometres(miles.value * 1.6) +given Conversion[Kilometres] = identity +given Conversion[Miles] = miles => Kilometres(miles.value * 1.6) - booster.advanceRocket(rocket1, Kilometres(100)) // Allocation of Kilometres object - booster.advanceRocket(rocket2, Miles(200)) // Allocation of Miles object +booster.advanceRocket(rocket1, Kilometres(100)) // Allocation of Kilometres object +booster.advanceRocket(rocket2, Miles(200)) // Allocation of Miles object + ``` --- -## Value-class wrappers limitations +## Value-class wrappers limitations - II ## ­ * BUT! Allocations happen in many cases (e.g. subtyping) ```scala - sealed trait Distance extends Any - case class Kilometres(value: Double) extends AnyVal with Distance - case class Miles(value: Double) extends AnyVal with Distance - - class Rocket(distanceTravelled: Kilometres): - def advance(distanceToAdvance: Kilometres): Rocket = new Rocket( - Kilometres(distanceTravelled.value + distanceToAdvance.value) - ) - - class Booster(): - def advanceRocket(rocket: Rocket, distanceToAdvance: Distance): Rocket = { - val distanceInKm = distanceToAdvance match { - case miles: Miles => Kilometres(miles.value * 1.6) - case km: Kilometres => km - } - rocket.advance(distanceInKm) +sealed trait Distance extends Any +case class Kilometres(value: Double) extends AnyVal with Distance +case class Miles(value: Double) extends AnyVal with Distance + +class Rocket(distanceTravelled: Kilometres): + def advance(distanceToAdvance: Kilometres): Rocket = new Rocket( + Kilometres(distanceTravelled.value + distanceToAdvance.value) + ) + +class Booster(): + def advanceRocket(rocket: Rocket, distanceToAdvance: Distance): Rocket = { + val distanceInKm = distanceToAdvance match { + case miles: Miles => Kilometres(miles.value * 1.6) + case km: Kilometres => km } + rocket.advance(distanceInKm) + } + ``` ```scala - val rocket1 = new Rocket(Kilometres(0)) - val rocket2 = new Rocket(Kilometres(0)) - val booster = new Booster() +val rocket1 = new Rocket(Kilometres(0)) +val rocket2 = new Rocket(Kilometres(0)) +val booster = new Booster() - booster.advanceRocket(rocket1, Kilometres(100)) // Allocation of Kilometres object - booster.advanceRocket(rocket2, Miles(200)) // Allocation of Miles object +booster.advanceRocket(rocket1, Kilometres(100)) // Allocation of Kilometres object +booster.advanceRocket(rocket2, Miles(200)) // Allocation of Miles object + ``` --- @@ -190,10 +194,10 @@ * BUT! Allocations happen in many cases (e.g. array assignment) ```scala - case class Kilometres(value: Double) extends AnyVal - case class Miles(value: Double) extends AnyVal +case class Kilometres(value: Double) extends AnyVal +case class Miles(value: Double) extends AnyVal - val distances: Array[Kilometres] = Array(Kilometres(10)) // Allocation of Kilometres object +val distances: Array[Kilometres] = Array(Kilometres(10)) // Allocation of Kilometres object ``` * Limitation especially significant for numeric computing @@ -211,9 +215,9 @@ * Scala 3 introduces the opaque keyword add in front of plain type alias ```scala - object Scala3OpaqueTypeAliasesDefinitions: - opaque type Kilometres = Double - opaque type Miles = Double +object Scala3OpaqueTypeAliasesDefinitions: + opaque type Kilometres = Double + opaque type Miles = Double ``` * Must be members of ***`class`***­*es*, ***`trait`***­*s*, or ***`object`***­*s*, or defined at the top-level. They cannot be defined in local blocks. @@ -249,7 +253,7 @@ object Scala3OpaqueTypeAliasesDefinitions: @scala.annotation.targetName("plusMiles") def + (b: Miles): Miles = a + b def toKm: Kilometres = a * 1.6 - + ``` --- @@ -261,17 +265,17 @@ object Scala3OpaqueTypeAliasesDefinitions: * So revisiting our ***`Rocket`*** and ***`Booster`*** example, we get type-safety... ```scala - import Scala3OpaqueTypeAliasesDefinitions._ +import Scala3OpaqueTypeAliasesDefinitions._ - class Rocket(distanceTravelled: Kilometres): - def advance(distanceToAdvance: Kilometres): Rocket = new Rocket( - distanceTravelled + distanceToAdvance - ) +class Rocket(distanceTravelled: Kilometres): + def advance(distanceToAdvance: Kilometres): Rocket = new Rocket( + distanceTravelled + distanceToAdvance + ) - class Booster(): - def advanceRocket(rocket: Rocket, distanceToAdvance: Miles): Rocket = { - // Kilometres and Miles are different types. So compiler prevents this bug - rocket.advance(distanceToAdvance) +class Booster(): + def advanceRocket(rocket: Rocket, distanceToAdvance: Miles): Rocket = { + // Kilometres and Miles are different types. So compiler prevents this bug + rocket.advance(distanceToAdvance) -- [E007] Type Mismatch Error: ------------------------------------------------- 11 | rocket.advance(distanceToAdvance) | ^^^^^^^^^^^^^^^^^ @@ -290,29 +294,30 @@ object Scala3OpaqueTypeAliasesDefinitions: * ...but without allocation cost, even in context of parametric polymorphism ```scala - import Scala3OpaqueTypeAliasesDefinitions.* - - class Rocket(distanceTravelled: Kilometres): - def advance(distanceToAdvance: Kilometres): Rocket = new Rocket( - distanceTravelled + distanceToAdvance - ) - - type Conversion[A] = A => Kilometres - class Booster(): - def advanceRocket[A: Conversion](rocket: Rocket, distanceToAdvance: A): Rocket = { - val distanceInKm = summon[Conversion[A]](distanceToAdvance) - rocket.advance(distanceInKm) - } - - val rocket1 = new Rocket(Kilometres(0)) - val rocket2 = new Rocket(Kilometres(0)) - val booster = new Booster() - - given Conversion[Kilometres] = identity - given Conversion[Miles] = _.toKm - - booster.advanceRocket(rocket1, Kilometres(100)) // No allocation of Kilometres object - booster.advanceRocket(rocket2, Miles(200)) // No allocation of Miles object +import Scala3OpaqueTypeAliasesDefinitions.* + +class Rocket(distanceTravelled: Kilometres): + def advance(distanceToAdvance: Kilometres): Rocket = new Rocket( + distanceTravelled + distanceToAdvance + ) + +type Conversion[A] = A => Kilometres +class Booster(): + def advanceRocket[A: Conversion](rocket: Rocket, distanceToAdvance: A): Rocket = { + val distanceInKm = summon[Conversion[A]](distanceToAdvance) + rocket.advance(distanceInKm) + } + +val rocket1 = new Rocket(Kilometres(0)) +val rocket2 = new Rocket(Kilometres(0)) +val booster = new Booster() + +given Conversion[Kilometres] = identity +given Conversion[Miles] = _.toKm + +booster.advanceRocket(rocket1, Kilometres(100)) // No allocation of Kilometres object +booster.advanceRocket(rocket2, Miles(200)) // No allocation of Miles object + ``` --- @@ -323,9 +328,10 @@ object Scala3OpaqueTypeAliasesDefinitions: * ...and no allocation costs when assigning to arrays ```scala - import Scala3OpaqueTypeAliasesDefinitions.* +import Scala3OpaqueTypeAliasesDefinitions.* - val distances: Array[Kilometres] = Array(Kilometres(10)) // No allocation of Kilometres object +val distances: Array[Kilometres] = Array(Kilometres(10)) // No allocation of Kilometres object + ``` * The wrapper type only exists at compile-time @@ -341,28 +347,28 @@ object Scala3OpaqueTypeAliasesDefinitions: * Buggy version with pattern matching though... ```scala - import Scala3OpaqueTypeAliasesDefinitions.* - - class Rocket(distanceTravelled: Kilometres): - def advance(distanceToAdvance: Kilometres): Rocket = new Rocket( - distanceTravelled + distanceToAdvance - ) - - type Distance = Kilometres | Miles - class Booster(): - // THIS GIVES A WARNING. THE 'Kilometres' CASE IS UNREACHABLE due to erasure. - // SO WE HAVE A BUG. Any 'Kilometres' passed to this method will be multiplied by 1.6 - def advanceRocket(rocket: Rocket, distanceToAdvance: Distance): Rocket = - val distanceInKm = distanceToAdvance match { - case miles: Miles => miles.toKm - case km: Kilometres => km +import Scala3OpaqueTypeAliasesDefinitions.* + +class Rocket(distanceTravelled: Kilometres): + def advance(distanceToAdvance: Kilometres): Rocket = new Rocket( + distanceTravelled + distanceToAdvance + ) + +type Distance = Kilometres | Miles +class Booster(): + // THIS GIVES A WARNING. THE 'Kilometres' CASE IS UNREACHABLE due to erasure. + // SO WE HAVE A BUG. Any 'Kilometres' passed to this method will be multiplied by 1.6 + def advanceRocket(rocket: Rocket, distanceToAdvance: Distance): Rocket = + val distanceInKm = distanceToAdvance match { + case miles: Miles => miles.toKm + case km: Kilometres => km [warn] -- [E030] Match case Unreachable Warning: dottyslidescodesnippets/src/main/scala/org/lunatech/dotty/opaquetypes/Units.scala:16:13 [warn] 16 | case km: Kilometres => km [warn] | ^^^^^^^^^^^^^^ [warn] | Unreachable case [warn] one warning found - } - rocket.advance(distanceInKm) + } + rocket.advance(distanceInKm) ``` ```scala val rocket1 = new Rocket(Kilometres(0)) From 7900edbec786f58a5b444e2ffd835235074262ac Mon Sep 17 00:00:00 2001 From: Eric Loots Date: Wed, 6 Sep 2023 15:27:04 +0200 Subject: [PATCH 9/9] Update chapter on multiversal equality --- slides/12-multiversal-equality.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slides/12-multiversal-equality.md b/slides/12-multiversal-equality.md index c71b8c23a..62553b09e 100644 --- a/slides/12-multiversal-equality.md +++ b/slides/12-multiversal-equality.md @@ -42,13 +42,13 @@ final case class Item(id: UUID) * ...but we forget to change the ***`Repository`***... -```scala +```scala class Repository(items: Seq[Item]): def findById(id: Id): Option[Item] = { // The following type-checks but will always return false // so we never find any items - items.find(_.id == id) + items.find(_.id == id) // Comparing an `id` with a `UUID` } ``` @@ -202,4 +202,4 @@ final case class Item(id: UUID) derives CanEqual * In this exercise, we will see how to 'opt-in' to Multiversal Equality and how to create an ***`CanEqual`*** Type class instance * Make sure you're positioned at exercise *"multiversal equality"* - * Follow the exercise instructions provided in the README.md file in the code folder \ No newline at end of file + * Follow the exercise instructions provided in the README.md file in the code folder