├── .gitignore ├── .scalafmt.conf ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt └── src └── main └── scala ├── c00-about-me.scala ├── c01-compatibility.scala ├── c02-new-syntax.scala ├── c03-enums.scala ├── c04-contextual-abstractions.scala ├── c05-opaque-types.scala ├── c06-union-and-intersection-types.scala ├── c07-the-small-bits.scala ├── c08-new-kinds-of-types.scala ├── c09-typeclass-derivation.scala ├── c10-macros.scala └── solutions ├── c02-new-syntax.scala ├── c03-enums.scala ├── c04-contextual-abstractions.scala ├── c05-opaque-types.scala └── c06-union-and-intersection-types.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .metals/ 2 | .bsp/ 3 | .vscode/ 4 | .bloop/ 5 | .ammonite/ 6 | metals.sbt 7 | target/ -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.0.0-RC1 2 | runner.dialect = scala3 3 | docstrings.style = Asterisk 4 | maxColumn = 120 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scala 3 crash course 2 | 3 | [![Join the chat at https://gitter.im/bszwej/scala-3-crash-course](https://badges.gitter.im/bszwej/scala-3-crash-course.svg)](https://gitter.im/bszwej/scala-3-crash-course?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Scala 3 RC3 is already here and we can expect the final release on the 12th of May if no major bugs are found! That's why it's a good timing for playing around and seeing what's there. In this workshop we'll see the most interesting and important features like enums, contextual abstractions (givens, extension methods, type classes, context functions), new types (opaque, unions and intersections). We'll also see what's dropped and what changed. During the exercises, you'll be refactoring Scala 2 to Scala 3 yourself using the new shiny constructs. 6 | 7 | This workshop is dedicated to Scala 2 developers and assumes prior knowledge about implicits, ADTs and type classes. 8 | 9 | ## Before the workshop 10 | 1. Checkout this repository 11 | ```sh 12 | git clone git@github.com:bszwej/scala-3-crash-course.git 13 | ``` 14 | 15 | 2. Compile with sbt 16 | ```sh 17 | sbt compile 18 | ``` 19 | 20 | 3. Import it in your favorite IDE 21 | - VSCode + metals (recommended) 22 | - Intellij 23 | 24 | ## Table of contents 25 | 1. Braceless syntax 26 | 2. Enums 27 | 3. Contextual language features: 28 | - given/using 29 | - extension methods 30 | - type classes 31 | - implicit conversion 32 | - scoping of given/using 33 | - contextual functions 34 | 4. Opaque types 35 | 5. Union and Intersection types 36 | 6. The small bits 37 | 7. **(In preparation)** New kinds of types: type lambdas, match types, dependent functions, polymorphic functions 38 | 8. **(In preparation)** Type class derivation 39 | 9. **(In preparation)** Macros 40 | 41 | ## Solutions 42 | 43 | Solutions to all exercises can be found in the [`solutions`](https://github.com/bszwej/scala-3-crash-course/tree/master/src/main/scala/solutions) package. 44 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | val scala3Version = "3.0.0" 2 | 3 | lazy val root = project 4 | .in(file(".")) 5 | .settings( 6 | name := "scala3-crash-course", 7 | version := "0.1.0", 8 | 9 | scalaVersion := scala3Version, 10 | ) 11 | 12 | scalacOptions ++= Seq( 13 | "-encoding", "utf8", 14 | "-Xfatal-warnings", 15 | "-deprecation", 16 | "-unchecked", 17 | "-language:implicitConversions", 18 | "-language:higherKinds", 19 | "-language:existentials", 20 | // "-indent", "-rewrite" 21 | // "-Xprint:typer" 22 | // "-Xprint:parser" 23 | ) 24 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.2 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") 2 | -------------------------------------------------------------------------------- /src/main/scala/c00-about-me.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * About me 3 | * 4 | * - Software engineer & a big fan of Scala and FP 5 | * - I've been using Scala for over 5 years 6 | * - I blog about Scala (bszwej.medium.com) 7 | * - Currently working at MOIA 8 | * 9 | * You can find me on: 10 | * - Twitter (@bszwej) 11 | * - Github (@bszwej) 12 | */ 13 | -------------------------------------------------------------------------------- /src/main/scala/c01-compatibility.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Chapter 1: Compatibility 3 | * 4 | * - Scala 2 source can be compiled with Scala 3 compiler. 5 | * - Scala 2.13 libraries work with Scala 3. 6 | * - Scala 3 libraries work with Scala 2.13.5 or newer. 7 | * 8 | * Migration guide: https://scalacenter.github.io/scala-3-migration-guide/ 9 | * Scala 3 community build: https://github.com/lampepfl/dotty/blob/master/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala#L88 10 | * 11 | * Breaking changes: 12 | * - Macros - completely new API 13 | * - Dropped constructs: 14 | * - DelayedInit - https://dotty.epfl.ch/docs/reference/dropped-features/delayed-init.html 15 | * - Existential types - https://dotty.epfl.ch/docs/reference/dropped-features/existential-types.html 16 | * - Procedure syntax - def f() { ... } 17 | * - Class shadowing - class Base { class Ops { ... } }; class Sub extends Base { class Ops { ... } } 18 | * - XML literals - xml""" ... """ 19 | * - Symbol literals - 'literal 20 | * - Auto application - def next(): T = ???; next 21 | * - Compound types - val environment: Logging with UserRepo with Clock with Random = ??? 22 | * - Auto tupling - https://contributors.scala-lang.org/t/lets-drop-auto-tupling/1799 23 | * - Early initializers - class C extends { ... } with SuperClass ... 24 | * - See more: https://dotty.epfl.ch/docs/Dropped%20Features/index.html 25 | */ 26 | -------------------------------------------------------------------------------- /src/main/scala/c02-new-syntax.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Chapter 2: New syntax 3 | * 4 | * More on that: https://contributors.scala-lang.org/t/feedback-sought-optional-braces/4702 5 | */ 6 | 7 | object BracelessSyntaxExamples { 8 | 9 | /** 10 | * Example 1: New braceless syntax for traits, classes & objects 11 | */ 12 | trait Foo { 13 | def bar(input: String): Unit 14 | } 15 | 16 | class Bar extends Foo { 17 | override def bar(input: String) = ??? 18 | } 19 | 20 | object Foo { 21 | def fromString(input: String): Foo = ??? 22 | } 23 | 24 | /** 25 | * Example 2: Braceless pattern matching. 26 | */ 27 | "something" match { 28 | case "something" => ??? 29 | case _ => ??? 30 | } 31 | 32 | /** 33 | * Example 3: Braceless conditionals. 34 | */ 35 | if (42 > 42) { 36 | val x = 10 37 | val y = 20 38 | x 39 | } else { 40 | val a = 10 41 | val b = 20 42 | b 43 | } 44 | 45 | /** 46 | * Example 4: Braceless methods. 47 | */ 48 | def m = { 49 | val x = 10 50 | val y = 20 51 | x + y 52 | } 53 | 54 | /** 55 | * Example 5: Braceless for comprehensions. 56 | */ 57 | for { 58 | x <- Some(1) 59 | y <- Some(2) 60 | } yield x + y 61 | 62 | /** 63 | * Example 6: The "end" syntax. 64 | */ 65 | if 42 == 42 then 66 | val x = 10 67 | val y = 20 68 | x 69 | else 70 | val a = 10 71 | val b = 20 72 | b 73 | 74 | def mm = 75 | val x = 10 76 | val y = 20 77 | x + y 78 | 79 | class Test: 80 | val x = "hello" 81 | 82 | /** 83 | * Example 7: The new @main annotation. 84 | */ 85 | } 86 | 87 | /** 88 | * Exercises 89 | */ 90 | 91 | // 92 | // Exercise 1: Convert the following code to the new Scala 3 braceless syntax. 93 | // 94 | object BracelessSyntaxExercise { 95 | 96 | sealed trait PaymentMethod 97 | case object CreditCard extends PaymentMethod 98 | case object Paypal extends PaymentMethod 99 | object PaymentMethod { 100 | def fromString(input: String): Option[PaymentMethod] = input match { 101 | case "credit_card" => Some(CreditCard) 102 | case "paypal" => Some(CreditCard) 103 | case _ => None 104 | } 105 | } 106 | 107 | trait PaymentService { 108 | def authorize(amount: BigDecimal, method: PaymentMethod): Unit 109 | } 110 | class PaymentServiceImpl extends PaymentService { 111 | def authorize(amount: BigDecimal, method: PaymentMethod): Unit = { 112 | println("Authorizing a payment.") 113 | for { 114 | _ <- validate(amount, method) 115 | _ <- callStripe(amount, method) 116 | } yield () 117 | } 118 | 119 | private def validate(amount: BigDecimal, method: PaymentMethod): Either[String, Unit] = 120 | if (amount > 0) { 121 | ??? 122 | } else { 123 | ??? 124 | } 125 | 126 | private def callStripe(amount: BigDecimal, method: PaymentMethod): Either[String, Unit] = ??? 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/scala/c03-enums.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Chapter 3: Enums 3 | */ 4 | 5 | // Enums in Scala 2 emulated with sealed traits/abstract classes 6 | // and a combination of case objects with case classes. 7 | sealed trait ShipmentStatus 8 | case object InPreparation extends ShipmentStatus 9 | case object Dispatched extends ShipmentStatus 10 | case object InTransit extends ShipmentStatus 11 | case object Delivered extends ShipmentStatus 12 | 13 | /** 14 | * Example 1: Simple enums 15 | * [] usage, compiler errors, usage (exhaustive pattern matching, values, valueOf, ordinal, fromOrdinal) 16 | * [] multiline enums 17 | * [] enum methods/companion object 18 | */ 19 | 20 | /** 21 | * Example 2: ADTs 22 | * [] syntax sugar 23 | * [] type widening 24 | * [] type params 25 | */ 26 | sealed abstract class Customer(val priority: Int) 27 | object Customer { 28 | case class Standard(name: String) extends Customer(priority = 3) 29 | case class Premium(name: String) extends Customer(priority = 1) 30 | case class Business(companyName: String, vatId: String) extends Customer(priority = 2) 31 | } 32 | 33 | /** 34 | * Exercises 35 | */ 36 | object enumsExercises: 37 | // 38 | // Exercise 1: Convert to Scala 3 enum syntax. 39 | // 40 | sealed trait OrderStatus { 41 | final def isLegalSuccessorOf(status: OrderStatus): Boolean = 42 | OrderStatus.legalSuccessors.getOrElse(status, Set.empty).contains(this) 43 | } 44 | object OrderStatus { 45 | case object Initiated extends OrderStatus 46 | case object Cancelled extends OrderStatus 47 | case object Confirmed extends OrderStatus 48 | case object Fulfilled extends OrderStatus 49 | case object Refunded extends OrderStatus 50 | case object Failed extends OrderStatus 51 | 52 | private val legalSuccessors: Map[OrderStatus, Set[OrderStatus]] = Map( 53 | Initiated -> Set(Confirmed, Cancelled), 54 | Confirmed -> Set(Fulfilled, Failed), 55 | Fulfilled -> Set(Refunded, Failed) 56 | ) 57 | } 58 | 59 | // 60 | // Exercise 2: Implement the following ADT using the new Scala 3 enum sytanx. 61 | // 62 | sealed abstract class PaymentAuthorizationError(retriable: Boolean) 63 | case class IllegalPaymentStatus(existingPaymentId: PaymentId, existingPaymentStatus: PaymentStatus) 64 | extends PaymentAuthorizationError(retriable = false) 65 | case class IllegalRequestData(reason: String) extends PaymentAuthorizationError(retriable = false) 66 | case class CustomerUnknown(unknownCustomerId: CustomerId) extends PaymentAuthorizationError(retriable = false) 67 | case class InvalidToken(invalidToken: Token) extends PaymentAuthorizationError(retriable = true) 68 | 69 | // Some type aliases to make the exercise compile... 70 | // We'll later see how to replace type aliases the new kind of types in Scala 3 71 | // called *opaque types* for better type safety. 72 | type PaymentId = String 73 | type PaymentStatus = String 74 | type CustomerId = String 75 | type Token = String 76 | -------------------------------------------------------------------------------- /src/main/scala/c04-contextual-abstractions.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Chapter 4: Contextual abstractions (a.k.a. implicits we all love(d) in Scala 2). 3 | * 4 | * The table below presents contextual language features of Scala 3 and how they relate to Scala 2. 5 | * 6 | * +----------------------------------------------------------+------------------------------------------+-------------------------------------------+ 7 | * | Contextual feature | Implementation in Scala 2 | Implementation in Scala 3 | 8 | * +----------------------------------------------------------+------------------------------------------+-------------------------------------------+ 9 | * | implicit parameters | implicit val | given/using clauses | 10 | * | extension methods (adding methods to existing types) | implicit class | extension clause | 11 | * | implicit conversion | implicit def | Conversion[From, To] type class | 12 | * | type classes | trait + implicit object + implicit class | trait + given/using + ext. method(s) | 13 | * | context functions (formerly known as implicit functions) | - | context functions | 14 | * |----------------------------------------------------------+------------------------------------------+-------------------------------------------+ 15 | * 16 | * Why implicits deprecated in favor of distinct Contextual Abstractions: https://dotty.epfl.ch/docs/reference/contextual/motivation.html 17 | * Relationship between Scala 3 contextual features and Scala 2 implicits: http://dotty.epfl.ch/docs/reference/contextual/relationship-implicits.html 18 | * Note: `implicit` keyword will be supported only until the last release of Scala 3.1.x. 19 | */ 20 | 21 | // format: off 22 | object ImplicitConfusion: 23 | implicit def fun1(baz: Int): String = ??? // Implicit conversion 🙈 24 | def fun2(implicit bar: String): String = ??? // Function, that requires implicit parameter to run. 25 | implicit def fun3(implicit foo: String): String = ??? // Type class instance, that requires another type class instance. Think of Encoder[List[A]]. 26 | implicit class Class(val foo: String) {} // Implicit class a.k.a. syntax 27 | // format: on 28 | 29 | /** 30 | * Chapter 3.1: Implicit parameters a.k.a. term inference 31 | * [] Given/using 32 | * [] Given/using and implicit can be used interchangibly 33 | * [] Term inference 34 | */ 35 | object ImplicitParams: 36 | import scala.concurrent.ExecutionContext 37 | import scala.concurrent.Future 38 | 39 | implicit val executionContext: ExecutionContext = ExecutionContext.parasitic 40 | def sendPostRequest(url: String)(implicit ec: ExecutionContext): Future[String] = Future.unit.map(_ => "OK") 41 | sendPostRequest("https://moia.io") 42 | 43 | /** 44 | * Chapter 3.2: Extension methods 45 | * [] New extension syntax 46 | * [] Type parameters in the new syntax 47 | */ 48 | @main def extensionMethods = 49 | implicit class StringOps(value: String) { 50 | def times(times: Int): Seq[String] = (1 to times).map(_ => value) 51 | def unit: Unit = () 52 | } 53 | 54 | println("a".times(20).toList) 55 | println("a".times(20).toList) 56 | 57 | /** 58 | * Exercises: extension methods 59 | */ 60 | @main def extensionMethodsExercises = 61 | // Exercise 1: Extend java.time.Instant with a method 62 | // calculating the duration between two instants: def -(until: Instant): java.time.Duration 63 | // Hint: You can use java.time.Duration.between(one, two). 64 | import java.time.* // In Scala 3 `*` is the wildcard import and not `_` anymore. 65 | 66 | // The following should compile: 67 | // println(Instant.now() - Instant.now()) 68 | 69 | // Exercise 2: Implement def +(thatTuple: Tuple2[A, B]) function on the Tuple2 70 | // Hint: Use the Numeric type class (https://www.scala-lang.org/api/current/scala/math/Numeric.html). 71 | 72 | // The following should compile: 73 | // println((2, 2.1) + (3, 4.0)) // result: (5, 6.1) 74 | 75 | /** 76 | * Chapter 3.3: Type classes 77 | * [] Type Classes in Scala 3 78 | * [] implicitly => summon 79 | */ 80 | object Typeclasses: 81 | case class Data(field: String) 82 | 83 | // 1. Type class declaration 84 | trait Show[A] { 85 | def show(a: A): String 86 | } 87 | 88 | // 2. Type class instance for the custom data type 89 | implicit val dataShow: Show[Data] = new Show[Data] { // or implicit object... 90 | override def show(data: Data): String = s"Data(field = ${data.field})" 91 | } 92 | 93 | // 3. Interface w/ summoner 94 | object Show { 95 | def show[A](a: A)(implicit ev: Show[A]): String = ev.show(a) 96 | } 97 | 98 | // 4. Syntax 99 | implicit class ShowOps[A](a: A)(implicit ev: Show[A]) { 100 | def show: String = ev.show(a) 101 | } 102 | 103 | // 5. Usage 104 | def usage[A: Show](a: A) = ??? 105 | 106 | // 6. Derivation 107 | 108 | /** 109 | * Exercises: type classes 110 | */ 111 | object TypeclassesExercises: 112 | // Exercise 1. For the following Monad type class declaration implement: 113 | // - instance for Option 114 | // - syntax 115 | trait Monad[F[_]]: 116 | def bind[A, B](fa: F[A])(f: A => F[B]): F[B] 117 | def unit[A](x: A): F[A] 118 | ??? 119 | 120 | /** 121 | * Chapter 3.4: Implicit conversions 122 | */ 123 | object ImplicitConversions: 124 | implicit def booleanToString(input: Boolean): String = input.toString 125 | 126 | def identity(input: String): String = input 127 | identity(true) 128 | val x: String = false 129 | 130 | /** 131 | * Chapter 3.5: Importing givens & extension methods 132 | * [] _ is replaced by * 133 | * [] givens must be explicitly imported 134 | * [] import by given's type 135 | * [] import by given's name 136 | * [] import all givens 137 | * [] import aliases 138 | */ 139 | object Instances: 140 | given x: String = "hello world" 141 | given y: Int = 42 142 | val test: Int = 42 143 | extension (str: String) def to42: Int = 42 144 | given Conversion[Boolean, String] with 145 | def apply(input: Boolean): String = input.toString 146 | object Scoping: 147 | // import Instances._ 148 | // summon[String] 149 | // "a".to42 150 | // val str: String = false 151 | ??? 152 | 153 | /** 154 | * Chapter 3.6: Context functions 155 | * 156 | * https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html 157 | */ 158 | object ContextFunctions: 159 | import scala.concurrent.{Future, ExecutionContext} 160 | import concurrent.ExecutionContext.Implicits.global 161 | 162 | case class TracingContext(traceId: String) 163 | 164 | def createUser(userData: String)(using TracingContext): Future[Unit] = 165 | for { 166 | _ <- validateUser(userData) 167 | _ <- insertUser(userData) 168 | } yield () 169 | def validateUser(userData: String)(using TracingContext): Future[Unit] = ??? 170 | def insertUser(userData: String)(using TracingContext): Future[Unit] = ??? 171 | -------------------------------------------------------------------------------- /src/main/scala/c05-opaque-types.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Chapter 5: Opaque types 3 | */ 4 | 5 | object AnyValDrawbacks: 6 | object ValueClasses { 7 | case class PaymentId(id: String) extends AnyVal 8 | val paymentId = PaymentId("abc") 9 | def printName(paymentId: PaymentId) = println(paymentId.id) 10 | // val somePaymentId: Option[PaymentId] = Some(PaymentId("abc")) // this will box :( 11 | } 12 | // More on that: https://failex.blogspot.com/2017/04/the-high-cost-of-anyval-subclasses.html 13 | 14 | object OpaqueTypes: 15 | /** 16 | * Example 1: From value classes to Opaque Types. 17 | */ 18 | case class FirstName(value: String) extends AnyVal 19 | 20 | /** 21 | * Example 2: Defining companion object with constructors and extensions. 22 | */ 23 | 24 | /** 25 | * Example 3: Type bounds. 26 | */ 27 | 28 | ??? 29 | 30 | object OpaqueTypesUsage: 31 | ??? 32 | 33 | /** 34 | * Exercises 35 | */ 36 | 37 | // 38 | // Exercise 1: Use Opaque types. 39 | // 40 | object OpaqueTypeExercises: 41 | import java.util.Locale 42 | final case class Country private (code: String) extends AnyVal 43 | object Country { 44 | private val validCodes = Locale.getISOCountries 45 | 46 | private def apply(code: String): Country = new Country(code) 47 | 48 | def fromIso2CountryCode(code: String): Option[Country] = Some(code).filter(validCodes.contains).map(Country.apply) 49 | 50 | def unsafeFromIso2CountryCode(code: String): Country = fromIso2CountryCode(code) 51 | .getOrElse( 52 | throw new IllegalStateException(s"Cannot parse country from String. Expected country code. Got '$code'.") 53 | ) 54 | 55 | val Germany: Country = Country("DE") 56 | val UnitedKingdom: Country = Country("GB") 57 | } 58 | 59 | @main def opaqueTypeExercisesMain = 60 | import OpaqueTypeExercises._ 61 | val country: Option[Country] = Country.fromIso2CountryCode("DE") 62 | println(country) 63 | println(country.map(_.code)) 64 | -------------------------------------------------------------------------------- /src/main/scala/c06-union-and-intersection-types.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Chapter 6: Union and intersection types 3 | */ 4 | 5 | object UnionTypes: 6 | /** 7 | * Example 1: Basics 8 | * [] Union type 9 | * [] Commutativity 10 | * [] Variance 11 | * [] Union types vs either 12 | */ 13 | 14 | // definition 15 | val x: Boolean = true 16 | 17 | // usage 18 | def f(in: Boolean): String = ??? 19 | 20 | // How many values does type `Either[Boolean, Boolean]` have? 21 | // How many values does type `Boolean | Boolean` have? 22 | 23 | /** 24 | * Example 2: What type will be inferred? 25 | * [] Can we improve inference with union types? 26 | */ 27 | val what = if (1 == 1) "a" else 10 28 | 29 | /** 30 | * Example 3: Possible use case: union types and eithers 31 | */ 32 | enum FooError: 33 | case FooError1 34 | case FooError2 35 | 36 | enum BarError: 37 | case BarError1 38 | case BarError2 39 | 40 | val error1: Either[FooError, String] = Left(FooError.FooError1) 41 | val error2: Either[BarError, String] = Left(BarError.BarError1) 42 | 43 | val value = 44 | for 45 | _ <- error1 46 | _ <- error2 47 | yield () 48 | 49 | // 50 | // Exercise 1: Model a PaymentAuthorizationError ADT from Chapter 3 (enums) using a union type. 51 | // What pros/cons do you see when you use a union type vs enums in modeling ADTs? 52 | // 53 | 54 | object IntersectionTypes: 55 | /** 56 | * Example 1: Basics 57 | * [] Example 58 | * [] Subtyping 59 | * [] Variance 60 | * [] Definition 61 | */ 62 | object Example1: 63 | trait A: 64 | def foo: String 65 | 66 | trait B: 67 | def bar: Int 68 | 69 | def x: A & B = ??? 70 | 71 | /** 72 | * Example 2: Conflicting members 73 | * [] Same name different type 74 | * [] Same name same type 75 | */ 76 | object Example21: 77 | trait A: 78 | def foo: String 79 | trait B: 80 | def foo: Int 81 | 82 | def x: A & B = ??? 83 | 84 | object Example22: 85 | trait A: 86 | def foo: Boolean 87 | trait B: 88 | def foo: Boolean 89 | 90 | def x: A & B = ??? 91 | 92 | /** 93 | * Example 3: Intersection types vs compound types (a.k.a. `with` types from Scala 2) 94 | * [] With vs & 95 | * [] Commutativity 96 | */ 97 | trait Foo: 98 | def f: AnyVal 99 | trait A extends Foo: 100 | override def f: Boolean 101 | trait B extends Foo: 102 | override def f: AnyVal 103 | 104 | // btw. there's a nice talk by Dean Wampler that thoroughly explains 105 | // all the properties of union and intersection types: https://youtu.be/8H9KPlGSBnM?t=1270 106 | -------------------------------------------------------------------------------- /src/main/scala/c07-the-small-bits.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Chapter 7: The small bits - minor changes you'll likely use 3 | */ 4 | 5 | /** 6 | * Example 1: New case class private constructor behavior 7 | */ 8 | case class Person private (name: String) 9 | 10 | // The following won't compile as apply and copy are now private. 11 | // They can only be used in the companion object. 12 | 13 | // val person: Person = Person("name") 14 | // val newPerson = person.copy(name = "other") 15 | 16 | /** 17 | * Example 2: Top level definitions 18 | * [] They replace package objects 19 | * [] Compiler generates a synthetic object for those (srcfilename$package) 20 | */ 21 | def topLevelDefinitions = 22 | ??? 23 | 24 | /** 25 | * Example 3: Strict equality 26 | * [] == implemented in terms of .equals 27 | * [] opt-in: import scala.language.strictEquality or -language:strictEquality flag 28 | * [] CanEqual type class using derives or given 29 | */ 30 | object StrictEquality: 31 | case class A(text: String) 32 | case class B(number: Int) 33 | 34 | // Both are compiling fine! 35 | A("") == A("") 36 | A("") == B(10) 37 | 38 | /** 39 | * Example 4: 22 limit is dropped 40 | * [] In Scala 3 it compiles 41 | * [] In Scala 2: error: too many elements for tuple: 30, allowed: 22 42 | */ 43 | type twentyFiveIntTuple = ( 44 | Int, 45 | Int, 46 | Int, 47 | Int, 48 | Int, 49 | Int, 50 | Int, 51 | Int, 52 | Int, 53 | Int, 54 | Int, 55 | Int, 56 | Int, 57 | Int, 58 | Int, 59 | Int, 60 | Int, 61 | Int, 62 | Int, 63 | Int, 64 | Int, 65 | Int, 66 | Int, 67 | Int, 68 | Int, 69 | Int, 70 | Int, 71 | Int, 72 | Int, 73 | Int 74 | ) 75 | type fOftwentyFiveArity = ( 76 | Int, 77 | Int, 78 | Int, 79 | Int, 80 | Int, 81 | Int, 82 | Int, 83 | Int, 84 | Int, 85 | Int, 86 | Int, 87 | Int, 88 | Int, 89 | Int, 90 | Int, 91 | Int, 92 | Int, 93 | Int, 94 | Int, 95 | Int, 96 | Int, 97 | Int, 98 | Int, 99 | Int, 100 | Int, 101 | Int, 102 | Int, 103 | Int, 104 | Int, 105 | Int 106 | ) => Unit 107 | -------------------------------------------------------------------------------- /src/main/scala/c08-new-kinds-of-types.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Chapter 8: New kinds of types: type lambdas, match types, dependent functions, polymorphic functions 3 | */ 4 | 5 | // tbd 6 | -------------------------------------------------------------------------------- /src/main/scala/c09-typeclass-derivation.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Chapter 9: Type class derivation 3 | */ 4 | 5 | // tbd 6 | -------------------------------------------------------------------------------- /src/main/scala/c10-macros.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Chapter 10: Macros 3 | */ 4 | 5 | // tbd 6 | -------------------------------------------------------------------------------- /src/main/scala/solutions/c02-new-syntax.scala: -------------------------------------------------------------------------------- 1 | package solutions 2 | 3 | /** 4 | * Chapter 2: New syntax 5 | * 6 | * More on that: https://contributors.scala-lang.org/t/feedback-sought-optional-braces/4702 7 | */ 8 | 9 | object BracelessSyntaxExamples: 10 | /** 11 | * Example 1: New braceless syntax for traits, classes & objects 12 | */ 13 | trait Foo: 14 | def bar(input: String): Unit 15 | 16 | class Bar extends Foo: 17 | override def bar(input: String) = ??? 18 | 19 | object Foo: 20 | def fromString(input: String): Foo = ??? 21 | 22 | /** 23 | * Example 2: Braceless pattern matching. 24 | */ 25 | "something" match 26 | case "something" => ??? 27 | case _ => ??? 28 | 29 | /** 30 | * Example 3: Braceless conditionals. 31 | */ 32 | if 42 > 42 then 33 | val x = 10 34 | val y = 20 35 | x 36 | else 37 | val a = 10 38 | val b = 20 39 | b 40 | 41 | /** 42 | * Example 4: Braceless methods. 43 | */ 44 | def m = 45 | val x = 10 46 | val y = 20 47 | x + y 48 | 49 | /** 50 | * Example 5: Braceless for comprehensions. 51 | */ 52 | for 53 | x <- Some(1) 54 | y <- Some(2) 55 | yield x + y 56 | 57 | /** 58 | * Example 6: The "end" syntax. 59 | */ 60 | if (42 == 42) 61 | val x = 10 62 | val y = 20 63 | x 64 | else 65 | val a = 10 66 | val b = 20 67 | b 68 | end if 69 | 70 | def mm = 71 | val x = 10 72 | val y = 20 73 | x + y 74 | end mm 75 | 76 | class Test: 77 | val x = "hello" 78 | end Test 79 | 80 | /** 81 | * Example 7: The new @main annotation. 82 | */ 83 | @main def myApp = 84 | ??? 85 | 86 | /** 87 | * Exercises 88 | */ 89 | 90 | // 91 | // Exercise 1: Convert the following code to the new Scala 3 braceless syntax. 92 | // 93 | object BracelessSyntaxExercise: 94 | sealed trait PaymentMethod 95 | case object CreditCard extends PaymentMethod 96 | case object Paypal extends PaymentMethod 97 | object PaymentMethod: 98 | def fromString(input: String): Option[PaymentMethod] = input match 99 | case "credit_card" => Some(CreditCard) 100 | case "paypal" => Some(CreditCard) 101 | case _ => None 102 | 103 | trait PaymentService: 104 | def authorize(amount: BigDecimal, method: PaymentMethod): Unit 105 | class PaymentServiceImpl extends PaymentService: 106 | def authorize(amount: BigDecimal, method: PaymentMethod): Unit = 107 | println("Authorizing a payment.") 108 | for 109 | _ <- validate(amount, method) 110 | _ <- callStripe(amount, method) 111 | yield () 112 | 113 | private def validate(amount: BigDecimal, method: PaymentMethod): Either[String, Unit] = 114 | if amount > 0 then ??? 115 | else ??? 116 | 117 | private def callStripe(amount: BigDecimal, method: PaymentMethod): Either[String, Unit] = ??? 118 | -------------------------------------------------------------------------------- /src/main/scala/solutions/c03-enums.scala: -------------------------------------------------------------------------------- 1 | package solutions 2 | 3 | /** 4 | * Chapter 3: Enums 5 | */ 6 | 7 | // Enums in Scala 2 emulated with sealed traits/abstract classes 8 | // and a combination of case objects with case classes. 9 | 10 | /** 11 | * Example 1: Simple enums 12 | * [x] usage, compiler errors, usage (exhaustive pattern matching, values, valueOf, ordinal, fromOrdinal) 13 | * [x] multiline enums 14 | * [x] enum methods/companion object 15 | */ 16 | enum ShipmentStatus: 17 | case InPreparation, Dispatched, InTransit, Delivered 18 | 19 | def asString: String = 20 | this match 21 | case InPreparation => "IN_PREPARATION" 22 | case Dispatched => "DISPATCHED" 23 | case InTransit => "IN_TRANSIT" 24 | case Delivered => "DELIVERED" 25 | 26 | import scala.util.Try 27 | object ShipmentStatus: 28 | def fromString(str: String): Option[ShipmentStatus] = Try(ShipmentStatus.valueOf(str)).toOption 29 | 30 | object enumUsage: 31 | ShipmentStatus.values 32 | ShipmentStatus.valueOf("InPreparation") 33 | ShipmentStatus.fromOrdinal(1) 34 | ShipmentStatus.Dispatched.ordinal 35 | 36 | /** 37 | * Example 2: ADTs 38 | * [x] syntax sugar 39 | * [x] type widening 40 | * [x] type params 41 | */ 42 | enum Customer(val priority: Int): 43 | case Standard(name: String) extends Customer(priority = 3) 44 | case Premium(name: String) extends Customer(priority = 1) 45 | case Business(companyName: String, vatId: String) extends Customer(priority = 2) 46 | 47 | enum List[+A]: 48 | case Nil 49 | case Cons(head: A, tail: List[A]) 50 | 51 | /** 52 | * Exercises 53 | */ 54 | object enumsExercises: 55 | // 56 | // Exercise 1: Convert to Scala 3 enum syntax. 57 | // 58 | enum OrderStatus: 59 | case Initiated, Cancelled, Confirmed, Fulfilled, Refunded, Failed 60 | 61 | def isLegalSuccessorOf(status: OrderStatus): Boolean = 62 | OrderStatus.legalSuccessors.getOrElse(status, Set.empty).contains(this) 63 | 64 | object OrderStatus: 65 | private val legalSuccessors: Map[OrderStatus, Set[OrderStatus]] = Map( 66 | Initiated -> Set(Confirmed, Cancelled), 67 | Confirmed -> Set(Fulfilled, Failed), 68 | Fulfilled -> Set(Refunded, Failed) 69 | ) 70 | 71 | // 72 | // Exercise 2: Implement the following ADT using the new Scala 3 enum sytanx. 73 | // 74 | enum PaymentAuthorizationError(retriable: Boolean): 75 | case IllegalPaymentStatus(existingPaymentId: PaymentId, existingPaymentStatus: PaymentStatus) 76 | extends PaymentAuthorizationError(retriable = false) 77 | case IllegalRequestData(reason: String) extends PaymentAuthorizationError(retriable = false) 78 | case CustomerUnknown(unknownCustomerId: CustomerId) extends PaymentAuthorizationError(retriable = false) 79 | case InvalidToken(invalidToken: Token) extends PaymentAuthorizationError(retriable = true) 80 | 81 | // Some type aliases to make the exercise compile... 82 | // We'll later see how to replace type aliases the new kind of types in Scala 3 83 | // called *opaque types* for better type safety. 84 | type PaymentId = String 85 | type PaymentStatus = String 86 | type CustomerId = String 87 | type Token = String 88 | -------------------------------------------------------------------------------- /src/main/scala/solutions/c04-contextual-abstractions.scala: -------------------------------------------------------------------------------- 1 | package solutions 2 | 3 | /** 4 | * Chapter 4: Contextual abstractions (a.k.a. implicits we all love(d) in Scala 2). 5 | * 6 | * The table below presents contextual language features of Scala 3 and how they relate to Scala 2. 7 | * 8 | * +----------------------------------------------------------+------------------------------------------+-------------------------------------------+ 9 | * | Contextual feature | Implementation in Scala 2 | Implementation in Scala 3 | 10 | * +----------------------------------------------------------+------------------------------------------+-------------------------------------------+ 11 | * | implicit parameters | implicit val | given/using clauses | 12 | * | extension methods (adding methods to existing types) | implicit class | extension clause | 13 | * | implicit conversion | implicit def | Conversion[From, To] type class | 14 | * | type classes | trait + implicit object + implicit class | trait + given/using + ext. method(s) | 15 | * | context functions (formerly known as implicit functions) | - | context functions | 16 | * |----------------------------------------------------------+------------------------------------------+-------------------------------------------+ 17 | * 18 | * Why implicits deprecated in favor of distinct Contextual Abstractions: https://dotty.epfl.ch/docs/reference/contextual/motivation.html 19 | * Relationship between Scala 3 contextual features and Scala 2 implicits: http://dotty.epfl.ch/docs/reference/contextual/relationship-implicits.html 20 | * Note: `implicit` keyword will be supported only until the last release of Scala 3.1.x. 21 | */ 22 | 23 | // format: off 24 | object ImplicitConfusion: 25 | implicit def fun1(baz: Int): String = ??? // Implicit conversion 🙈 26 | def fun2(implicit bar: String): String = ??? // Function, that requires implicit parameter to run. 27 | implicit def fun3(implicit foo: String): String = ??? // Type class instance, that requires another type class instance. Think of Encoder[List[A]]. 28 | implicit class Class(val foo: String) {} // Implicit class a.k.a. syntax 29 | // format: on 30 | 31 | /** 32 | * Chapter 3.1: Implicit parameters a.k.a. term inference 33 | * [x] Given/using 34 | * [x] Given/using and implicit can be used interchangibly 35 | * [x] Term inference 36 | */ 37 | object ImplicitParams: 38 | import scala.concurrent.ExecutionContext 39 | import scala.concurrent.Future 40 | 41 | given executionContext: ExecutionContext = ExecutionContext.parasitic 42 | summon[ExecutionContext] 43 | def sendPostRequest(url: String)(using ExecutionContext): Future[String] = Future.unit.map(_ => "OK") 44 | sendPostRequest("https://moia.io") 45 | 46 | /** 47 | * Chapter 3.2: Extension methods 48 | * [x] New extension syntax 49 | * [x] Type parameters in the new syntax 50 | */ 51 | @main def extensionMethods = 52 | extension [A](value: A) 53 | def times(times: Int): Seq[A] = (1 to times).map(_ => value) 54 | def unit: Unit = () 55 | 56 | println("a".times(20).toList) 57 | println("a".unit) 58 | 59 | /** 60 | * Exercises: extension methods 61 | */ 62 | @main def extensionMethodsExercises = 63 | // Exercise 1: Extend java.time.Instant with a method 64 | // calculating the duration between two instants: def -(until: Instant): java.time.Duration 65 | // Hint: You can use java.time.Duration.between(one, two). 66 | import java.time.* // In Scala 3 `*` is the wildcard import and not `_` anymore. 67 | extension (thisInstant: Instant) def -(thatInstant: Instant): Duration = Duration.between(thisInstant, thatInstant) 68 | 69 | val diff = Instant.now() - Instant.now().plusSeconds(10) 70 | println(diff.getSeconds) // result: 10 71 | 72 | // Exercise 2: Implement def +(thatTuple: Tuple2[A, B]) function on the Tuple2 73 | // Hint: Use the Numeric type class (https://www.scala-lang.org/api/current/scala/math/Numeric.html). 74 | extension [A, B](tuple: Tuple2[A, B])(using a: Numeric[A], b: Numeric[B]) 75 | def +(thatTuple: Tuple2[A, B]) = (a.plus(tuple._1, thatTuple._1), b.plus(tuple._2, thatTuple._2)) 76 | // There's also a syntax for the Numeric typeclass available in scala.math.Numeric.Implicits.* 77 | 78 | // The following should compile: 79 | println((2, 2.1) + (3, 4.0)) // result: (5, 6.1) 80 | 81 | /** 82 | * Chapter 3.3: Type classes 83 | * [] Type Classes in Scala 3 84 | * [] implicitly => summon 85 | */ 86 | object Typeclasses: 87 | case class Data(field: String) 88 | 89 | // 1. Type class declaration 90 | trait Show[A]: 91 | extension(a: A) def show: String 92 | 93 | // 2. Type class instance for the custom data type 94 | given Show[Data] with 95 | extension(data: Data) def show: String = s"Data(field = ${data.field})" 96 | 97 | // 3. Interface w/ summoner 98 | object Show: 99 | def show[A](a: A)(using ev: Show[A]): String = ev.show(a) 100 | 101 | // 4. Syntax 102 | // Now show function is an extension method inside the typeclass declaration 103 | 104 | // 5. Usage 105 | def usage[A: Show](a: A) = a.show 106 | 107 | // 6. Derivation 108 | // case class Person(name: String) derives Eq, Show, Encoder 109 | 110 | /** 111 | * Exercises: type classes 112 | */ 113 | object TypeclassesExercises: 114 | // Exercise 1. For the following Monad type class declaration implement: 115 | // - instance for Option 116 | // - syntax 117 | trait Monad[F[_]]: 118 | extension[A](fa: F[A]) def bind[B](f: A => F[B]): F[B] 119 | extension [A](a: A) def unit: F[A] 120 | 121 | given Monad[Option] with 122 | extension[A](fa: Option[A]) def bind[B](f: A => Option[B]): Option[B] = fa.fold(None)(f) 123 | extension [A](a: A) def unit: Option[A] = Some(a) 124 | 125 | Some(2).bind(x => Some(x * 5)).bind(_ => None) // None 126 | 10.unit // Some(10) 127 | 128 | /** 129 | * Chapter 3.4: Implicit conversions 130 | */ 131 | object ImplicitConversions: 132 | given Conversion[Boolean, String] with 133 | def apply(bool: Boolean): String = bool.toString 134 | 135 | def identity(input: String): String = input 136 | identity(true) 137 | val x: String = false 138 | 139 | /** 140 | * Chapter 3.5: Importing givens & extension methods 141 | * [x] _ is replaced by * 142 | * [x] givens must be explicitly imported 143 | * [x] import by given's type 144 | * [x] import by given's name 145 | * [x] import all givens 146 | * [x] import aliases 147 | */ 148 | object Instances: 149 | given x: String = "hello world" 150 | given y: Int = 42 151 | val test: Int = 42 152 | extension (str: String) def to42: Int = 42 153 | given Conversion[Boolean, String] with 154 | def apply(input: Boolean): String = input.toString 155 | object Scoping: 156 | import Instances.{given String, given Int, given Conversion[Boolean, String], *} 157 | summon[String] 158 | summon[Int] 159 | "a".to42 160 | val str: String = false 161 | 162 | /** 163 | * Chapter 3.6: Context functions 164 | * 165 | * https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html 166 | */ 167 | object ContextFunctions: 168 | import scala.concurrent.{Future, ExecutionContext} 169 | import concurrent.ExecutionContext.Implicits.global 170 | 171 | case class TraceId(value: String) 172 | case class TracingContext(traceId: TraceId) 173 | 174 | type Context[A] = TracingContext ?=> A // implicit String => Int 175 | object Context: 176 | def traceId(using TracingContext): TraceId = summon[TracingContext].traceId 177 | 178 | def createUser(userData: String): Context[Future[Unit]] = 179 | for 180 | _ <- validateUser(userData) 181 | _ = summon[TracingContext] 182 | _ = Context.traceId 183 | _ <- insertUser(userData) 184 | yield () 185 | def validateUser(userData: String): Context[Future[Unit]] = ??? 186 | def insertUser(userData: String): Context[Future[Unit]] = ??? 187 | -------------------------------------------------------------------------------- /src/main/scala/solutions/c05-opaque-types.scala: -------------------------------------------------------------------------------- 1 | package solutions 2 | 3 | /** 4 | * Chapter 5: Opaque types 5 | */ 6 | 7 | object AnyValDrawbacks: 8 | object ValueClasses: 9 | case class PaymentId(id: String) extends AnyVal 10 | val paymentId = PaymentId("abc") 11 | def printName(paymentId: PaymentId) = println(paymentId.id) 12 | // val somePaymentId: Option[PaymentId] = Some(PaymentId("abc")) // this will box :( 13 | // More on that: https://failex.blogspot.com/2017/04/the-high-cost-of-anyval-subclasses.html 14 | 15 | object OpaqueTypes: 16 | /** 17 | * Example 1: From value classes to Opaque Types. 18 | */ 19 | opaque type FirstName = String 20 | 21 | /** 22 | * Example 2: Defining companion object with constructors and extensions. 23 | */ 24 | object FirstName: 25 | def fromString(str: String): FirstName = str 26 | 27 | extension (firstName: FirstName) def value: String = firstName 28 | 29 | /** 30 | * Example 3: Type bounds. 31 | */ 32 | opaque type MiddleName >: String = String // LOWER type bound. MiddleName is a supertype of String. 33 | opaque type LastName <: String = String // UPPER type bound. LastName is a subtype of String. 34 | object LastName: 35 | def fromString(str: String): LastName = str 36 | 37 | object OpaqueTypesUsage: 38 | import OpaqueTypes._ 39 | // val firstName: FirstName = "name" // does not compile as types do not match 40 | val middleName: MiddleName = "middle name" // works, because String is a subtype of MiddleName 41 | val lastName: String = LastName.fromString("last name") // works, because LastName is a subtype of String 42 | 43 | /** 44 | * Exercises 45 | */ 46 | 47 | // 48 | // Exercise 1: Use Opaque types. 49 | // 50 | object OpaqueTypeExercises: 51 | import java.util.Locale 52 | 53 | opaque type Country = String 54 | object Country: 55 | private val validCodes = Locale.getISOCountries 56 | 57 | def fromIso2CountryCode(code: String): Option[Country] = Some(code).filter(validCodes.contains).map(_ => code) 58 | 59 | def unsafeFromIso2CountryCode(code: String): Country = fromIso2CountryCode(code) 60 | .getOrElse( 61 | throw new IllegalStateException(s"Cannot parse country from String. Expected country code. Got '$code'.") 62 | ) 63 | 64 | val Germany: Country = "DE" 65 | val UnitedKingdom: Country = "GB" 66 | 67 | extension (country: Country) def code: String = country 68 | 69 | @main def opaqueTypeExercisesMain = 70 | import OpaqueTypeExercises._ 71 | val country: Option[Country] = Country.fromIso2CountryCode("DE") 72 | println(country) 73 | println(country.map(_.code)) 74 | -------------------------------------------------------------------------------- /src/main/scala/solutions/c06-union-and-intersection-types.scala: -------------------------------------------------------------------------------- 1 | package solutions 2 | 3 | /** 4 | * Chapter 6: Union and intersection types 5 | */ 6 | 7 | object UnionTypes: 8 | /** 9 | * Example 1: Basics 10 | * [x] Union type 11 | * [x] Commutativity 12 | * [x] Union types vs either 13 | */ 14 | 15 | // definition 16 | val x: Boolean | Int = true 17 | 18 | // usage 19 | def f(in: Boolean | Int): String = in match { 20 | case _: Boolean => "bool" 21 | case _: Int => "int" 22 | } 23 | 24 | // How many values does type `Either[Boolean, Boolean]` have? 4 25 | // How many values does type `Boolean | Boolean` have? 2 26 | 27 | /** 28 | * Example 2: What type will be inferred? 29 | * [x] Can we improve inference with union types? 30 | */ 31 | val what: String | Int = if (1 == 1) "a" else 10 32 | 33 | /** 34 | * Example 3: Possible use case: union types and eithers 35 | */ 36 | enum FooError: 37 | case FooError1 38 | case FooError2 39 | 40 | enum BarError: 41 | case BarError1 42 | case BarError2 43 | 44 | val error1: Either[FooError, String] = Left(FooError.FooError1) 45 | val error2: Either[BarError, String] = Left(BarError.BarError1) 46 | 47 | val value: Either[FooError | BarError, Unit] = 48 | for 49 | _ <- error1 50 | _ <- error2 51 | yield () 52 | 53 | // 54 | // Exercise 1: Model a PaymentAuthorizationError ADT from Chapter 2 (enums) using a union type. 55 | // What pros/cons do you see when you use a union type vs enums in modeling ADTs? 56 | // 57 | case class IllegalPaymentStatus(existingPaymentId: String, existingPaymentStatus: String) 58 | case class IllegalRequestData(reason: String) 59 | case class CustomerUnknown(unknownCustomerId: String) 60 | case class InvalidToken(invalidToken: String) 61 | type PaymentAuthorizationError = IllegalPaymentStatus | IllegalRequestData | CustomerUnknown | InvalidToken 62 | 63 | object IntersectionTypes: 64 | /** 65 | * Example 1: Basics 66 | * [x] Example 67 | * [x] Subtyping 68 | * [x] Variance 69 | */ 70 | object Example1: 71 | trait A: 72 | def foo: String 73 | 74 | trait B: 75 | def bar: Int 76 | 77 | // example 78 | val ab: A & B = ??? 79 | ab.foo 80 | ab.bar 81 | 82 | // subtyping 83 | val a: A = ab 84 | val b: B = ab 85 | 86 | // variance - the following property is valid only for covariant types 87 | def x: List[A] & List[B] = ??? 88 | def y: List[A & B] = x 89 | 90 | /** 91 | * Example 2: Conflicting members 92 | * [x] Same name different type 93 | * [x] Same name same type 94 | */ 95 | object Example21: 96 | trait A: 97 | def foo: String 98 | trait B: 99 | def foo: Int 100 | 101 | val x: A & B = ??? 102 | x.foo // String & Int 103 | 104 | object Example22: 105 | trait A: 106 | def f: Boolean 107 | 108 | trait B: 109 | def f: Boolean 110 | 111 | val x: A & B = ??? 112 | x.f // Boolean, but which one? 113 | 114 | class X extends A, B: 115 | // It's the caller that decides! 116 | override def f: Boolean = true 117 | 118 | val y: A & B = new X 119 | 120 | /** 121 | * Example 3: Intersection types vs compound types (a.k.a. `with` types from Scala 2) 122 | * [x] With vs & 123 | * [x] Commutativity 124 | */ 125 | trait Foo: 126 | def f: AnyVal 127 | trait A extends Foo: 128 | override def f: Boolean 129 | trait B extends Foo: 130 | override def f: AnyVal 131 | 132 | // `with` is implemented using & in Scala 3. 133 | 134 | // In Scala 2 135 | // :type (???: A with B).f - Boolean 136 | // :type (???: B with A).f - AnyVal 137 | 138 | // In Scala 3 139 | // :type (???: A & B).f - Boolean & AnyVal 140 | // :type (???: B & A).f - AnyVal & Boolean 141 | --------------------------------------------------------------------------------