Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ project/metals.sbt
.bsp/
src/worksheet/
coverage/
.scala-build/
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@ The `master` branch and the `3.X.Y` tag releases are for the third edition. The

> [!WARNING]
> Scala 3 is evolving, as are the tools that support it. I try to keep the `main` branch up to date with the latest versions, including changing the examples as required to handle new and changed features (see, e.g., [issue #131](https://github.com/deanwampler/programming-scala-book-code-examples/issues/131)). Hence, sometimes an example (or how to run it) will be different from what you see in the book. So, if you are reading the book and want the examples exactly as they appear there, with the same tool versions used at that time, then grab the [`3.0.0-final`](https://github.com/deanwampler/programming-scala-book-code-examples/tree/3.0.0-final) release.
>
> In particular, running a scala program on the command line has changed as of 3.5.0. So, for example, at the top of page 12 of the book, change this command for running a program at the shell prompt:
>
> ```
> $ cp="target/scala-3.5.0/classes/" # Note the book has "3.0.0"
> $ scala -classpath $cp progscala3.introscala.Hello2 Hello Scala World!
> ```
> to this:
> ```
> $ cp="target/scala-3.5.0/classes/" # Note the book has "3.0.0"
> $ scala -classpath $cp -M progscala3.introscala.Hello2 -- Hello Scala World!
> ```
> Note the required `-M` (or `--main-class`) flag before the “`main`” class and the `--` to separate `scala` arguments from your programs arguments. Use these changes for all subsequent examples in the book that use the `scala` command to run code.
>
> It appears that `sbt` syntax has **not** changed when using `runMain` at the SBT prompt, for example:
> ```
> runMain progscala3.introscala.Hello2 Hello Scala World!
> ```
> (Use of `sbt` is discussed further below.)

> [!TIP]
> Several sections offer troubleshooting tips if you encounter problems.
Expand All @@ -24,7 +43,7 @@ The `master` branch and the `3.X.Y` tag releases are for the third edition. The
In the book's text, when an example corresponds to a file in this distribution, the listing begins with a path in a comment with the following format:

```scala
// src/main/scala/progscala3/.../FooBar.scala
// src/main/scala/progscala3.introscala.UpperMain1
```

Following the usual conventions, tests are in `src/test/...`.
Expand Down Expand Up @@ -135,23 +154,26 @@ tasks -V # REALLY show ALL tasks

The `~` prefix causes the task to be run continuously each time source code changes are saved. This promotes continuous TDD (test-driven development) and is one of my favorite features!

Outside of `sbt`, you could, in principle, run the REPL and load the script files manually at the prompt:
Outside of `sbt`, you could, in principle, run the REPL and load the script files manually at the prompt, for example:

```shell
$ scala
scala> :load src/script/scala/.../Foo.scala
scala> :load src/script/scala/progscala3/introscala/Upper1.scala
```

However, it's easier to run most of the scripts using `sbt console`, because `sbt` will configure the `CLASSPATH` with the third-party libraries and compiled code examples that a script file might use.

Also, new for the Scala 3 REPL, for those `src/main/...` files that define one (and only one) _entry point_, meaning a `main` method (Scala 2 compatible) or annotated with `@main` (new Scala 3 technique), you can compile and run them in one step:
Also, new for the Scala 3 REPL, for those `src/main/...` files that define one (and only one) _entry point_, meaning a `main` method (Scala 2 compatible) or annotated with `@main` (new Scala 3 technique), you can compile and run them in one step, for example:

```shell
$ scala src/main/scala/progscala3/introscala/UpperMain2.scala Hello World!
$ scala src/main/scala/progscala3/introscala/UpperMain2.scala -- Hello World!
HELLO WORLD!
$
```

> [!NOTE]
> The `--` argument separator is required for Scala 3.5.0 and later. It is not used for Scala 3.4.X and earlier.

## Feedback

I welcome feedback on the Book and these examples. Please post comments, corrections, etc. to one of the following places:
Expand All @@ -178,5 +200,6 @@ There is also my dedicated site for the book where occasional updates, clarifica
| May 22, 2021 | _Final_ updates for _Programming Scala, Third Edition_! |
| July 24, 2021 | Scala 3.0.1. Notes on using IntelliJ. |
| November 6, 2021 | Scala 3.1.0 and a fix for locale settings ([PR 42](https://github.com/deanwampler/programming-scala-book-code-examples/pull/42)). |
| September 15, 2024 | Scala 3.5.0 changes, e.g. the [new Scala CLI](https://docs.scala-lang.org/sips/scala-cli.html). |


Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package progscala3.typesystem.intersectionunion

/**
* A new example since publication demonstrating
* intersection and union types.
*/
object IntersectionUnion:

trait M:
def m(s: String): String = s
trait T1 extends M:
override def m(s: String): String = s"[ ${super.m(s)} ]"
trait T2 extends M:
override def m(s: String): String = s"( ${super.m(s)} )"
trait T3 extends M:
override def m(s: String): String = s"| ${super.m(s)} |"
open class C extends M:
override def m(s: String): String = s"{ ${super.m(s)} }"

val c123 = new C with T1 with T2 with T3
val c321 = new C with T3 with T2 with T1

def checkM(): Unit =
val m: M = new C
assert(m.m("hello") == "{ hello }", m.m("hello"))

assert(c123.m("hello") == "| ( [ { hello } ] ) |")
assert(c321.m("hello") == "[ ( | { hello } | ) ]")

def checkIntersectionCommutativity(): Unit =
val ct1t2t3_c123: C & T1 & T2 & T3 = c123
val t1ct2t3_c123: T1 & C & T2 & T3 = c123
val t1t2ct3_c123: T1 & T2 & C & T3 = c123
val t1t2t3c_c123: T1 & T2 & T3 & C = c123

val ct3t2t1_c123: C & T3 & T2 & T1 = c123
val t3ct2t1_c123: T3 & C & T2 & T1 = c123
val t3t2ct1_c123: T3 & T2 & C & T1 = c123
val t3t2t1c_c123: T3 & T2 & T1 & C = c123

val ct1t2t3_c321: C & T1 & T2 & T3 = c321
val t1ct2t3_c321: T1 & C & T2 & T3 = c321
val t1t2ct3_c321: T1 & T2 & C & T3 = c321
val t1t2t3c_c321: T1 & T2 & T3 & C = c321

val ct3t2t1_c321: C & T3 & T2 & T1 = c321
val t3ct2t1_c321: T3 & C & T2 & T1 = c321
val t3t2ct1_c321: T3 & T2 & C & T1 = c321
val t3t2t1c_c321: T3 & T2 & T1 & C = c321

def checkIntersectionSubtyping(): Unit =
val t1a: T1 = c123
val t2a: T2 = c123
val t3a: T3 = c123
val c2a: C = c123

val t123: T1 & T2 & T3 = c123
val ct1: C & T1 = c123
val ct2: C & T2 = c123
val ct3: C & T3 = c123

def checkIntersectionFunctionUsage(): Unit =
def f(t123: T1 & T2 & T3): String = t123.m("hello!")
val list123: Seq[T1 & T2 & T3] = Seq(c123, c321)
assert(list123.map(f) == List("| ( [ { hello! } ] ) |", "[ ( | { hello! } | ) ]"))

def checkIntersectionCovariance(): Unit =
val listt1t2t3: Seq[T1 & T2 & T3] = Seq(c123, c321)
val list1: Seq[T1] = listt1t2t3
val list2: Seq[T2] = listt1t2t3
val list3: Seq[T3] = listt1t2t3
val list123: Seq[T1] & Seq[T2] & Seq[T3] = listt1t2t3

// f(list1.head) // ERROR: "Found T1, Required T1 & T2 & T3"
// f(list2.head) // ERROR: "Found T2, Required T1 & T2 & T3"

case class Bad(message: String)
case class Good(i: Int)

def checkUnionGoodBad(): Unit =
val error = Bad("Failed!")
val result = Good(0)

val seq1 = Seq(error, result) // Inferred type: Seq[T1nyRef] or Seq[Object]!
val seq: Seq[Good | Bad] = Seq(error, result)

def work(i: Int): Good | Bad =
if i > 0 then Bad(s"$i must be <= 0") else Good(i)

def process(result: Good | Bad): String = result match
case Bad(message) => message
case Good(value) => s"Success! value = $value"

val results = Seq(0, 1).map(work)
val strings = results.map(process)
println(s"results = ${results.mkString(", ")}, strings = ${strings.mkString(", ")}")

def checkUnionLaws(): Unit =
summon[(T1 & (T2 | T3)) =:= ((T1 & T2) | (T1 & T3))]
summon[(T1 | (T2 & T3)) =:= ((T1 | T2) & (T1 | T3))]

val x1: T1 & (T2 | T3) = new T1 with T2 {}
val x2: T1 & (T2 | T3) = new T1 with T3 {}
val x3: T1 & (T2 | T3) = new T1 with T2 with T3 {}
val x4: (T1 & T2) | (T1 & T3) = new T1 with T2 {}
val x5: (T1 & T2) | (T1 & T3) = new T1 with T3 {}
val x6: (T1 & T2) | (T1 & T3) = new T1 with T2 with T3 {}

val x7: T1 | (T2 & T3) = new T1 {}
val x8: T1 | (T2 & T3) = new T2 with T3 {}
val x9: T1 | (T2 & T3) = new T1 with T2 with T3 {}
val x10: (T1 | T2) & (T1 | T3) = new T1 {}
val x11: (T1 | T2) & (T1 | T3) = new T2 with T3 {}
val x12: (T1 | T2) & (T1 | T3) = new T1 with T2 with T3 {}

def checkUnionCovariance(): Unit =
val seqT1s: Seq[T1] = Seq(new T1 {})
val seqT2s: Seq[T2] = Seq(new T2 {})
val seqT3s: Seq[T3] = Seq(new T3 {})
val seqT1T2T3s1: Seq[T1 | T2 | T3] = seqT1s
val seqT1T2T3s2: Seq[T1 | T2 | T3] = seqT2s
val seqT1T2T3s3: Seq[T1 | T2 | T3] = seqT3s

val tT1T2T3s: Seq[T1 | T2 | T3] = Seq(new T1 {}, new T2 {}, new T3 {})
// val tT1s: Seq[T1] = tT1T2T3s // ERROR
// val tT2s: Seq[T2] = tT1T2T3s // ERROR
// val tT3s: Seq[T3] = tT1T2T3s // ERROR

def checkUnionContravariantFunctions(): Unit =
val fT1T2T31: (T1 | T2 | T3) => String = _ match
case t1: T1 => "T1"
case t2: T2 => "T2"
case t3: T3 => "T3"
val fT1T2T32: (T1 => String) & (T2 => String) & (T3 => String) = fT1T2T31

val seqT1T2T3s: Seq[T1 | T2 | T3] = Seq(new T1 {}, new T2 {}, new T3 {})
seqT1T2T3s.map(fT1T2T31)
seqT1T2T3s.map(fT1T2T32)
seqT1T2T3s.map((x: AnyRef) => s"<$x>")

def main(args: Array[String]): Unit =
checkM()
checkIntersectionCommutativity()
checkIntersectionSubtyping()
checkIntersectionFunctionUsage()
checkIntersectionCovariance()
checkUnionGoodBad()
checkUnionLaws()
checkUnionCovariance()
checkUnionContravariantFunctions()