Skip to content

Commit 84bd045

Browse files
authored
Merge pull request #132 from deanwampler/release-3.5.0
Release 3.5.0
2 parents d414fdb + deea00e commit 84bd045

File tree

3 files changed

+179
-5
lines changed

3 files changed

+179
-5
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ project/metals.sbt
2626
.bsp/
2727
src/worksheet/
2828
coverage/
29+
.scala-build/

README.md

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,25 @@ The `master` branch and the `3.X.Y` tag releases are for the third edition. The
1515

1616
> [!WARNING]
1717
> 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.
18+
>
19+
> 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:
20+
>
21+
> ```
22+
> $ cp="target/scala-3.5.0/classes/" # Note the book has "3.0.0"
23+
> $ scala -classpath $cp progscala3.introscala.Hello2 Hello Scala World!
24+
> ```
25+
> to this:
26+
> ```
27+
> $ cp="target/scala-3.5.0/classes/" # Note the book has "3.0.0"
28+
> $ scala -classpath $cp -M progscala3.introscala.Hello2 -- Hello Scala World!
29+
> ```
30+
> 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.
31+
>
32+
> It appears that `sbt` syntax has **not** changed when using `runMain` at the SBT prompt, for example:
33+
> ```
34+
> runMain progscala3.introscala.Hello2 Hello Scala World!
35+
> ```
36+
> (Use of `sbt` is discussed further below.)
1837
1938
> [!TIP]
2039
> Several sections offer troubleshooting tips if you encounter problems.
@@ -24,7 +43,7 @@ The `master` branch and the `3.X.Y` tag releases are for the third edition. The
2443
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:
2544
2645
```scala
27-
// src/main/scala/progscala3/.../FooBar.scala
46+
// src/main/scala/progscala3.introscala.UpperMain1
2847
```
2948
3049
Following the usual conventions, tests are in `src/test/...`.
@@ -135,23 +154,26 @@ tasks -V # REALLY show ALL tasks
135154

136155
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!
137156

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

140159
```shell
141160
$ scala
142-
scala> :load src/script/scala/.../Foo.scala
161+
scala> :load src/script/scala/progscala3/introscala/Upper1.scala
143162
```
144163

145164
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.
146165

147-
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:
166+
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:
148167

149168
```shell
150-
$ scala src/main/scala/progscala3/introscala/UpperMain2.scala Hello World!
169+
$ scala src/main/scala/progscala3/introscala/UpperMain2.scala -- Hello World!
151170
HELLO WORLD!
152171
$
153172
```
154173

174+
> [!NOTE]
175+
> The `--` argument separator is required for Scala 3.5.0 and later. It is not used for Scala 3.4.X and earlier.
176+
155177
## Feedback
156178

157179
I welcome feedback on the Book and these examples. Please post comments, corrections, etc. to one of the following places:
@@ -178,5 +200,6 @@ There is also my dedicated site for the book where occasional updates, clarifica
178200
| May 22, 2021 | _Final_ updates for _Programming Scala, Third Edition_! |
179201
| July 24, 2021 | Scala 3.0.1. Notes on using IntelliJ. |
180202
| 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)). |
203+
| September 15, 2024 | Scala 3.5.0 changes, e.g. the [new Scala CLI](https://docs.scala-lang.org/sips/scala-cli.html). |
181204

182205

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package progscala3.typesystem.intersectionunion
2+
3+
/**
4+
* A new example since publication demonstrating
5+
* intersection and union types.
6+
*/
7+
object IntersectionUnion:
8+
9+
trait M:
10+
def m(s: String): String = s
11+
trait T1 extends M:
12+
override def m(s: String): String = s"[ ${super.m(s)} ]"
13+
trait T2 extends M:
14+
override def m(s: String): String = s"( ${super.m(s)} )"
15+
trait T3 extends M:
16+
override def m(s: String): String = s"| ${super.m(s)} |"
17+
open class C extends M:
18+
override def m(s: String): String = s"{ ${super.m(s)} }"
19+
20+
val c123 = new C with T1 with T2 with T3
21+
val c321 = new C with T3 with T2 with T1
22+
23+
def checkM(): Unit =
24+
val m: M = new C
25+
assert(m.m("hello") == "{ hello }", m.m("hello"))
26+
27+
assert(c123.m("hello") == "| ( [ { hello } ] ) |")
28+
assert(c321.m("hello") == "[ ( | { hello } | ) ]")
29+
30+
def checkIntersectionCommutativity(): Unit =
31+
val ct1t2t3_c123: C & T1 & T2 & T3 = c123
32+
val t1ct2t3_c123: T1 & C & T2 & T3 = c123
33+
val t1t2ct3_c123: T1 & T2 & C & T3 = c123
34+
val t1t2t3c_c123: T1 & T2 & T3 & C = c123
35+
36+
val ct3t2t1_c123: C & T3 & T2 & T1 = c123
37+
val t3ct2t1_c123: T3 & C & T2 & T1 = c123
38+
val t3t2ct1_c123: T3 & T2 & C & T1 = c123
39+
val t3t2t1c_c123: T3 & T2 & T1 & C = c123
40+
41+
val ct1t2t3_c321: C & T1 & T2 & T3 = c321
42+
val t1ct2t3_c321: T1 & C & T2 & T3 = c321
43+
val t1t2ct3_c321: T1 & T2 & C & T3 = c321
44+
val t1t2t3c_c321: T1 & T2 & T3 & C = c321
45+
46+
val ct3t2t1_c321: C & T3 & T2 & T1 = c321
47+
val t3ct2t1_c321: T3 & C & T2 & T1 = c321
48+
val t3t2ct1_c321: T3 & T2 & C & T1 = c321
49+
val t3t2t1c_c321: T3 & T2 & T1 & C = c321
50+
51+
def checkIntersectionSubtyping(): Unit =
52+
val t1a: T1 = c123
53+
val t2a: T2 = c123
54+
val t3a: T3 = c123
55+
val c2a: C = c123
56+
57+
val t123: T1 & T2 & T3 = c123
58+
val ct1: C & T1 = c123
59+
val ct2: C & T2 = c123
60+
val ct3: C & T3 = c123
61+
62+
def checkIntersectionFunctionUsage(): Unit =
63+
def f(t123: T1 & T2 & T3): String = t123.m("hello!")
64+
val list123: Seq[T1 & T2 & T3] = Seq(c123, c321)
65+
assert(list123.map(f) == List("| ( [ { hello! } ] ) |", "[ ( | { hello! } | ) ]"))
66+
67+
def checkIntersectionCovariance(): Unit =
68+
val listt1t2t3: Seq[T1 & T2 & T3] = Seq(c123, c321)
69+
val list1: Seq[T1] = listt1t2t3
70+
val list2: Seq[T2] = listt1t2t3
71+
val list3: Seq[T3] = listt1t2t3
72+
val list123: Seq[T1] & Seq[T2] & Seq[T3] = listt1t2t3
73+
74+
// f(list1.head) // ERROR: "Found T1, Required T1 & T2 & T3"
75+
// f(list2.head) // ERROR: "Found T2, Required T1 & T2 & T3"
76+
77+
case class Bad(message: String)
78+
case class Good(i: Int)
79+
80+
def checkUnionGoodBad(): Unit =
81+
val error = Bad("Failed!")
82+
val result = Good(0)
83+
84+
val seq1 = Seq(error, result) // Inferred type: Seq[T1nyRef] or Seq[Object]!
85+
val seq: Seq[Good | Bad] = Seq(error, result)
86+
87+
def work(i: Int): Good | Bad =
88+
if i > 0 then Bad(s"$i must be <= 0") else Good(i)
89+
90+
def process(result: Good | Bad): String = result match
91+
case Bad(message) => message
92+
case Good(value) => s"Success! value = $value"
93+
94+
val results = Seq(0, 1).map(work)
95+
val strings = results.map(process)
96+
println(s"results = ${results.mkString(", ")}, strings = ${strings.mkString(", ")}")
97+
98+
def checkUnionLaws(): Unit =
99+
summon[(T1 & (T2 | T3)) =:= ((T1 & T2) | (T1 & T3))]
100+
summon[(T1 | (T2 & T3)) =:= ((T1 | T2) & (T1 | T3))]
101+
102+
val x1: T1 & (T2 | T3) = new T1 with T2 {}
103+
val x2: T1 & (T2 | T3) = new T1 with T3 {}
104+
val x3: T1 & (T2 | T3) = new T1 with T2 with T3 {}
105+
val x4: (T1 & T2) | (T1 & T3) = new T1 with T2 {}
106+
val x5: (T1 & T2) | (T1 & T3) = new T1 with T3 {}
107+
val x6: (T1 & T2) | (T1 & T3) = new T1 with T2 with T3 {}
108+
109+
val x7: T1 | (T2 & T3) = new T1 {}
110+
val x8: T1 | (T2 & T3) = new T2 with T3 {}
111+
val x9: T1 | (T2 & T3) = new T1 with T2 with T3 {}
112+
val x10: (T1 | T2) & (T1 | T3) = new T1 {}
113+
val x11: (T1 | T2) & (T1 | T3) = new T2 with T3 {}
114+
val x12: (T1 | T2) & (T1 | T3) = new T1 with T2 with T3 {}
115+
116+
def checkUnionCovariance(): Unit =
117+
val seqT1s: Seq[T1] = Seq(new T1 {})
118+
val seqT2s: Seq[T2] = Seq(new T2 {})
119+
val seqT3s: Seq[T3] = Seq(new T3 {})
120+
val seqT1T2T3s1: Seq[T1 | T2 | T3] = seqT1s
121+
val seqT1T2T3s2: Seq[T1 | T2 | T3] = seqT2s
122+
val seqT1T2T3s3: Seq[T1 | T2 | T3] = seqT3s
123+
124+
val tT1T2T3s: Seq[T1 | T2 | T3] = Seq(new T1 {}, new T2 {}, new T3 {})
125+
// val tT1s: Seq[T1] = tT1T2T3s // ERROR
126+
// val tT2s: Seq[T2] = tT1T2T3s // ERROR
127+
// val tT3s: Seq[T3] = tT1T2T3s // ERROR
128+
129+
def checkUnionContravariantFunctions(): Unit =
130+
val fT1T2T31: (T1 | T2 | T3) => String = _ match
131+
case t1: T1 => "T1"
132+
case t2: T2 => "T2"
133+
case t3: T3 => "T3"
134+
val fT1T2T32: (T1 => String) & (T2 => String) & (T3 => String) = fT1T2T31
135+
136+
val seqT1T2T3s: Seq[T1 | T2 | T3] = Seq(new T1 {}, new T2 {}, new T3 {})
137+
seqT1T2T3s.map(fT1T2T31)
138+
seqT1T2T3s.map(fT1T2T32)
139+
seqT1T2T3s.map((x: AnyRef) => s"<$x>")
140+
141+
def main(args: Array[String]): Unit =
142+
checkM()
143+
checkIntersectionCommutativity()
144+
checkIntersectionSubtyping()
145+
checkIntersectionFunctionUsage()
146+
checkIntersectionCovariance()
147+
checkUnionGoodBad()
148+
checkUnionLaws()
149+
checkUnionCovariance()
150+
checkUnionContravariantFunctions()

0 commit comments

Comments
 (0)