diff --git a/README.md b/README.md
index cb032bba..e7c96d89 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
# "Programming Scala, 3rd Edition" Code Examples
-[](https://gitter.im/deanwampler/programming-scala-book-code-examples?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](https://scala-steward.org)
+
* [Dean Wampler](mailto:programming.scala@gmail.com)
-* [@deanwampler](https://twitter.com/deanwampler)
-* [LinkedIn](https://www.linkedin.com/in/deanwampler/)
-* [Book Page](http://programming-scala.org)
+* Dean Wampler's [Bluesky](https://bsky.app/profile/deanwampler.bsky.social), [Mastodon](https://discuss.systems/@deanwampler), and [LinkedIn](https://www.linkedin.com/in/deanwampler/) accounts.
+* [Repo discussions](https://github.com/deanwampler/programming-scala-book-code-examples/discussions)
+* [My Book Page](http://programming-scala.org)
* [Blog about Scala 3](https://medium.com/scala-3)
This repo contains all the code examples in O'Reilly's [Programming Scala, Third Edition](http://programming-scala.org). (The second edition is [available here](http://shop.oreilly.com/product/0636920033073.do).) There are also many code files in this distribution that aren't included in the book.
@@ -15,32 +15,36 @@ 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.)
+
+In particular, running a `scala` command-line program has changed as of version 3.5.0. So, for example, at the top of page 12 of the book, this command is shown for running a program at the shell prompt:
+
+```
+$ cp="target/scala-3.0.0/classes/"
+$ scala -classpath $cp progscala3.introscala.Hello2 Hello Scala World!
+```
+
+Instead, use the following, where the latest Scala version (at the time of this writing...) is `3.7.4`:
+
+```
+$ cp="target/scala-3.7.4/classes/"
+$ scala -classpath $cp --main-class progscala3.introscala.Hello2 -- Hello Scala World!
+```
+
+Note the required `--main-class` (or `-M`) flag before the “`main`” class `progscala3.introscala.Hello2` and the `--` to separate `scala` command arguments from your program's arguments. Change all other command-line examples in the book the same way.
+
+However, it appears that `sbt` syntax has **not** changed when using `runMain` at the SBT prompt. So, for example, the following still works as documented in the book:
+
+```
+runMain progscala3.introscala.Hello2 Hello Scala World!
+```
+(Use of `sbt` is discussed further below.)
> [!TIP]
> Several sections offer troubleshooting tips if you encounter problems.
## How the Code Is Used in the Book
-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:
+In the book's text, when an example corresponds to a file in this repo, the listing begins with a path in a comment with the following format:
```scala
// src/main/scala/progscala3.introscala.UpperMain1
@@ -48,7 +52,7 @@ In the book's text, when an example corresponds to a file in this distribution,
Following the usual conventions, tests are in `src/test/...`.
-Use these comments to find the corresponding source file. This archive also contains *MUnit* and *ScalaCheck* unit tests to validate some of the code.
+Use these comments shown in the book to find the corresponding source file in the repo. The repo also contains *MUnit* and *ScalaCheck* unit tests to validate some of the code.
## Naming Conventions
@@ -76,13 +80,13 @@ These are used to mark sections that are selectively included in the book. Somet
## Required and Optional Tools
-To build and run the examples, all you need is a recent version of the the JDK and [`sbt`](http://www.scala-sbt.org/release/docs/Getting-Started/Setup.html). When you run `sbt`, it will bootstrap itself with the correct version of its jar file, Scala, and project dependencies, which are specified in the `build.sbt` file in the root directory and other build files in the `project` directory.
+To build and run the examples, all you need is a recent version of the the Java SDK and [`sbt`](http://www.scala-sbt.org/release/docs/Getting-Started/Setup.html). When you run `sbt`, it will bootstrap itself with the correct version of itself, Scala, and project dependencies, which are specified in the `build.sbt` file in the root directory and other build files in the `project` directory.
Follow these [`sbt` installation instructions](http://www.scala-sbt.org/release/docs/Getting-Started/Setup.html).
If you want to install Scala separately and Scala's *Scaladocs*, go to the [scala-lang.org _Getting Started_ guide](https://docs.scala-lang.org/scala3/getting-started.html) for details. However, this isn't required.
-If you want to play with the Spark example, `src/script/scala-2/progscala3/bigdata/SparkWordCount.scala`, you'll need to download a Spark distribution from https://spark.apache.org. Assuming that `$SPARK_HOME` refers to the root directory of your Spark installation, run the following command in the root directory of this project:
+If you want to play with the Spark example, `src/script/scala-2/progscala3/bigdata/SparkWordCount.scala`, you'll need to download a Spark distribution from [spark.apache.org](https://spark.apache.org). Assuming that `$SPARK_HOME` refers to the root directory of your Spark installation, run the following command in the root directory of this repo to start the Scala REPL with Spark enabled:
```shell
$ $SPARK_HOME/bin/spark-shell
@@ -96,9 +100,7 @@ Then copy and paste the content of `src/script/scala-2/progscala3/bigdata/SparkW
### Editors, IntelliJ, Visual Studio Code, and Other IDEs
-> **NOTE:** Support for Scala 3 may be limited for a while in the following tools.
-
-Most editors and IDEs now have some sort of Scala support:
+Most editors and IDEs now have some sort of Scala 3 support, either "natively" or through optional plugins:
* [IntelliJ](https://www.jetbrains.com/idea/): Either the Community or Ultimate additions will work. Install the Scala plugin, which has built-in support for `sbt`.
* [Visual Studio Code](https://code.visualstudio.com/): Use the new [Scala Metals](https://scalameta.org/metals/) plugin instead of older plugins.
@@ -110,7 +112,7 @@ After installing the required plugins, load this project in your IDE, which shou
### Troubleshooting with IntelliJ
-One reader reported a problem when trying to run examples in IntelliJ: `scalac: Flag -encoding set repeatedly`. I could confirm this problem and I fixed it as follows:
+One reader reported a problem when trying to run examples in IntelliJ: `scalac: Flag -encoding set repeatedly`. I confirmed this problem (at the time; it may no longer be an issue...) and I fixed it as follows:
1. Open the preferences ("cmd-," on MacOS)
2. Search for "scala"
@@ -174,32 +176,46 @@ $
> [!NOTE]
> The `--` argument separator is required for Scala 3.5.0 and later. It is not used for Scala 3.4.X and earlier.
+### Testing the Scala Scripts and "Mains"
+
+There are a lot of _script_ files under `src/script/scala/...`, which are used in the book as suggestions to try in the console as you are reading the book. However, they are hard to test in the usual way. Similarly, there are many `@main` routines and they are also not covered by tests. So, to "semi-automate" testing them, try the following two `zsh` scripts, both of which take a long time to run:
+
+```shell
+check-scripts.sh
+check-mains.sh
+```
+
+Both have `--help` options to see what options they accept. Both do their best to check that the output is expected, but a complication is the fact that many of the Scala scripts and "mains" have deliberate errors for illustrative purposes. The `check-*.sh` scripts attempt to compensate for this, for example by knowing which scripts should fail and allowing them to fail "successfully", but to be _absolutely certain_ all the examples are running correctly, it is really necessary to visually inspect the saved output from the runs (as described in console output of the `check-*.sh`) to see if the results _look_ correct.
+
## Feedback
I welcome feedback on the Book and these examples. Please post comments, corrections, etc. to one of the following places:
-* This GitHub repo's [Gitter channel](https://gitter.im/deanwampler/programming-scala-book-code-examples), [Discussion forum](https://github.com/deanwampler/programming-scala-book-code-examples/discussions), or [Issues](https://github.com/deanwampler/programming-scala-book-code-examples/issues).
-* The book's Twitter account, [@ProgScala](https://twitter.com/ProgScala).
-* The O'Reilly book and errata sites (coming soon).
+* This GitHub repo's [discussion forum](https://github.com/deanwampler/programming-scala-book-code-examples/discussions) or [post an issue](https://github.com/deanwampler/programming-scala-book-code-examples/issues).
+* The [O'Reilly book page](https://oreil.ly/programming-scala-3) and the [errata page](https://www.oreilly.com/catalog/errata.csp?isbn=9781492077893).
+* Dean Wampler's [Bluesky](https://bsky.app/profile/deanwampler.bsky.social), [Mastodon](https://discuss.systems/@deanwampler), or [LinkedIn](https://www.linkedin.com/in/deanwampler/) accounts.
There is also my dedicated site for the book where occasional updates, clarifications, corrections, and lame excuses will be posted: [programming-scala.org](http://programming-scala.org).
## A Little History
-| Key Dates | Description |
-| :---------------- | :---------- |
-| August 11, 2014 | 2nd edition examples |
-| May 27, 2019 | Updated for Scala 2.12 and 2.13 |
-| June 18, 2019 | New support for Maven builds, courtesy of [oldbig](https://github.com/oldbig) |
-| October 12, 2019 | Updated for Scala 2.13.1, sbt 1.3.2, and other dependencies. Also now compiles with JDK 11 |
-| October 13, 2019 | Renamed the repo from `prog-scala-2nd-ed-code-examples` to `programming-scala-book-code-examples` |
-| December 31, 2019 | Renamed the `progscala2` package to `progscala3` and reworked most of the `*.sc` scripts for better testability and other improvements |
-| March 1, 2020 | Completed conversion to Scala 3 |
-| March 20, 2020 | Started incorporating new Scala 3 syntax, idioms |
-| May 15, 2021 | Scala `3.0.0` final updates. Almost done! |
-| 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). |
-
-
+Some milestones (but probably not all of them...).
+
+| Key Dates | Description |
+| :----------------- | :---------- |
+| August 11, 2014 | 2nd edition examples |
+| May 27, 2019 | Updated for Scala 2.12 and 2.13 |
+| June 18, 2019 | New support for Maven builds, courtesy of [oldbig](https://github.com/oldbig) |
+| October 12, 2019 | Updated for Scala 2.13.1, sbt 1.3.2, and other dependencies. Also now compiles with JDK 11 |
+| October 13, 2019 | Renamed the repo from `prog-scala-2nd-ed-code-examples` to `programming-scala-book-code-examples` |
+| December 31, 2019 | Renamed the `progscala2` package to `progscala3` and reworked most of the `*.sc` scripts for better testability and other improvements |
+| March 1, 2020 | Completed conversion to Scala 3 |
+| March 20, 2020 | Started incorporating new Scala 3 syntax, idioms |
+| May 15, 2021 | Scala `3.0.0` final updates. Almost done! |
+| 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). |
+| December 21, 2024 | Scala 3.6.2 changes, supporting new syntax options. |
+| June 17, 2025 | Scala 3.7.X breaking changes and fixed some old bugs in some of the "scripts". |
+| September 20, 2025 | Scala 3.7.3 breaking changes. Yes, a "patch" release, but in fairness, triggered by my strict compiler flags. |
diff --git a/build.sbt b/build.sbt
index 11f8c156..572cc249 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,4 +1,4 @@
-val scala3 = "3.5.2"
+val scala3 = "3.8.3"
lazy val root = project
.in(file("."))
.settings(
@@ -20,14 +20,14 @@ lazy val root = project
"com.typesafe.akka" %% "akka-slf4j" % "2.6.20",
).map(dep => dep.cross(CrossVersion.for3Use2_13)) ++ Seq(
// Libraries that already fully support Scala 3:
- "org.typelevel" %% "cats-core" % "2.12.0",
+ "org.typelevel" %% "cats-core" % "2.13.0",
"org.scala-lang" %% "scala3-staging" % scalaVersion.value,
"org.scala-lang.modules" %% "scala-parser-combinators" % "2.4.0",
- "ch.qos.logback" % "logback-classic" % "1.5.12",
- "org.scalacheck" %% "scalacheck" % "1.18.1" % Test,
- "org.scalameta" %% "munit" % "1.0.2" % Test,
- "org.scalameta" %% "munit-scalacheck" % "1.0.0" % Test,
- "com.eed3si9n.expecty" %% "expecty" % "0.16.0" % Test,
+ "ch.qos.logback" % "logback-classic" % "1.5.32",
+ "org.scalacheck" %% "scalacheck" % "1.19.0" % Test,
+ "org.scalameta" %% "munit" % "1.3.0" % Test,
+ "org.scalameta" %% "munit-scalacheck" % "1.3.0" % Test,
+ "com.eed3si9n.expecty" %% "expecty" % "0.17.1" % Test,
),
// For Scala 3
@@ -50,10 +50,11 @@ lazy val root = project
// "-old-syntax", // Require `(...)` around conditions.
// "-language:Scala2", // Compile Scala 2 code, highlight what needs updating
// "-language:strictEquality", // Require +derives Eql+ for using == or != comparisons
- // "-rewrite", // Attempt to fix code automatically. Use with -indent and ...-migration.
// "-scalajs", // Compile in Scala.js mode (requires scalajs-library.jar on the classpath).
- "-source:future", // Choices: future and future-migration. I use this to force future deprecation warnings, etc.
- "-Xfatal-warnings", // Fail on warnings, not just errors
+ "-source:future-migration", // Choices: future and future-migration. I use this to force future deprecation warnings, etc.
+ "-rewrite", // Rewrite source, when necessary, for future migration - DeanW: added Sept 2025
+ // "-Xfatal-warnings", // Deprecated in Scala 3.8. Use -Werror instead:
+ "-Werror", // Fail on warnings, not just errors
// "-Xmigration", // Warn about constructs whose behavior may have changed since version.
// "-Ysafe-init", // Warn on field access before initialization
// "-Yexplicit-nulls", // For explicit nulls behavior.
diff --git a/check-mains.sh b/check-mains.sh
new file mode 100755
index 00000000..7bc1f159
--- /dev/null
+++ b/check-mains.sh
@@ -0,0 +1,248 @@
+#!/usr/bin/env zsh
+
+out_root="target/main-tests"
+out_ext="out"
+timestamp=$(date +"%Y-%m-%d_%H-%M-%S")
+error_log="$out_root/mains-errors-$timestamp.log"
+def_mains=(
+ ScriptWrapper
+ progscala3.appdesign.IntDoubleStringMain
+ progscala3.appdesign.dbc.TryBankAccount
+ progscala3.appdesign.dbc.TryMyLogger
+ progscala3.appdesign.parthenon.RunPayroll
+ progscala3.basicoop.HelloServiceMain
+ progscala3.basicoop.TryComplex
+ progscala3.basicoop.scaladb.TryScalaDBRevisited
+ progscala3.basicoop.tagging.TryTagging
+ progscala3.basicoop.tagging.TryTagging2
+ progscala3.collections.TryListBuilder
+ progscala3.concurrency.akka.ServiceClient
+ progscala3.concurrency.boundary.BoundaryExamples
+ progscala3.concurrency.futures.TryFutureFold
+ progscala3.concurrency.futures.TryFuturesCallbacks
+ progscala3.concurrency.futures2.TryFuturesForComp
+ progscala3.concurrency.process.TryProcess
+ progscala3.contexts.TryDerived
+ progscala3.contexts.accounting.TryImplicitConversions
+ progscala3.contexts.json.TryJSONBuilder
+ progscala3.contexts.scaladb.TryScalaDB
+ progscala3.contexts.typeclass.new1.TryJSONTypeClasses
+ progscala3.contexts.typeclass.new2.TryJSONTypeClasses
+ progscala3.contexts.typeclass.new3.TryJSONTypeClasses
+ progscala3.contexts.typeclass.new4.TryJSONTypeClasses
+ progscala3.contexts.typeclass.old.TryJSONTypeClasses
+ progscala3.dsls.payroll.internal.TryPayroll
+ progscala3.dsls.payroll.parsercomb.TryPayroll
+ progscala3.forcomps.RemoveBlanks
+ progscala3.forcomps.TryLoginFormValidatorNec
+ progscala3.forcomps.TryLoginFormValidatorSingle
+ progscala3.fp.categories.TryFunctionF2A
+ progscala3.fp.categories.TryFunctionF2B
+ progscala3.fp.categories.TryFunctionF2C
+ progscala3.fp.categories.TryFunctionF2D
+ progscala3.fp.categories.TryFunctor2
+ progscala3.fp.loops.JavaFactorial
+ progscala3.introscala.Hello
+ progscala3.introscala.Hello2
+ progscala3.introscala.UpperMain1
+ progscala3.introscala.UpperMain1$package
+ progscala3.introscala.shapes.ProcessShapesDriver
+ progscala3.javainterop.JavaWithScalaTuples
+ progscala3.meta.TryInvariant
+ progscala3.meta.TryInvariant1
+ progscala3.meta.TryStaging
+ progscala3.meta.TryTracer
+ progscala3.meta.TryUsingClassTagViews
+ progscala3.meta.performance.InlinePerf
+ progscala3.objectsystem.CommandArgs
+ progscala3.objectsystem.JavaArrays
+ progscala3.objectsystem.objects.TryPerson
+ progscala3.rounding.FileSizes
+ progscala3.rounding.TryCatch
+ progscala3.rounding.TryCatchARM
+ progscala3.rounding.saferexceptions.SaferExceptions
+ progscala3.rounding.saferexceptions.SaferExceptionsNested
+ progscala3.typesystem.intersectionunion.IntersectionUnion
+ progscala3.typesystem.payroll.TryPhantomTypes
+ progscala3.typesystem.payroll.TryPhantomTypesPipeline
+ progscala3.typesystem.selftype.TryButtonSubjectObserver
+)
+
+declare -A mains_args
+mains_args["progscala3.meta.performance.InlinePerf"]="true 10"
+mains_args["progscala3.objectsystem.CommandArgs"]="--help"
+
+expected_errors_in=(
+ progscala3.meta.TryInvariant
+ progscala3.meta.TryInvariant1
+ progscala3.objectsystem.JavaArrays
+)
+
+error() {
+ echo "ERROR: $@"
+ help
+ exit 1
+}
+
+help() {
+ cat << EOF
+Checks that the "mains" run successfully by running them with the "runMain" sbt command.
+
+So, this bash script starts the REPL (using "sbt console") for each "main" and then
+uses :runMain to execute it. The output for that console sessions is written to
+$out_root/path/to/file.$out_ext.
+
+A list of files with errors or warnings is written to $error_log.
+
+** HOWEVER, to be really safe, all the outputs should still be inspected manually. **
+
+Usage: $0 [-h|--help] [-v|--verbose] [-c|--clean] [-n|--no-exec] [dir ...]
+Where:
+-h | --help Print this message and exit.
+-v | --verbose Print each file name to the console as it is processed and dump
+ to stdout the test output (in the script's corresponding
+ "$out_root/...").
+-c | --clean Delete all previous output.
+-n | --no-exec Don't execute the commands, just echo what would be done.
+--check | --check-only
+ Don't run the mains; just check for reported errors only
+ on any existing output files under $out_root.
+main ... Run these "mains". (default "${def_mains[@]}")
+EOF
+}
+
+: ${VERBOSE:=false}
+: ${CLEAN:=false}
+: ${CHECK_ONLY=false}
+: ${NOOP:=}
+mains=()
+
+while [[ $# -gt 0 ]]
+do
+ case $1 in
+ -h|--h*)
+ help
+ exit 0
+ ;;
+ -v|--v*)
+ VERBOSE=true
+ ;;
+ --check*)
+ CHECK_ONLY=true
+ ;;
+ -c|--cl*)
+ CLEAN=true
+ ;;
+ -n|--n*)
+ NOOP=echo
+ ;;
+ -*)
+ error "Unknown argument $1"
+ ;;
+ *)
+ mains+=($1)
+ ;;
+ esac
+ shift
+done
+
+[[ ${#mains[@]} -gt 0 ]] || mains=( ${def_mains[@]} )
+$VERBOSE && echo "Running mains: ${mains[@]}"
+
+if $CLEAN
+then
+ $VERBOSE && echo "Cleaning old output in $out_root..."
+ [[ -n "$out_root" ]] && rm -rf "$out_root" # safety check!
+fi
+
+rm -f $error_log
+
+print_count() {
+ let count=$1; shift
+ main=$1; shift
+ out=$1; shift
+ message="$1"; shift
+ printf '%5d: %s %s %s\n' $count "$main" "$out" "$message" >> $error_log
+}
+
+count_problem() {
+ main=$1
+ out=$2
+ let count=$(grep -cE "^.+ (error|warning)s? found$" "$out")
+ [[ $count -gt 0 ]] && print_count $count $main $out
+ return $count
+}
+
+report() {
+ let run_status=$1
+ main=$2
+ out=$3
+ for skip in ${expected_errors_in[@]}
+ do
+ if [[ "$skip" = "$main" ]]
+ then
+ print_count 0 "$main" "$out" "NOTE: because of known deliberate errors, unexpected errors might be missed!"
+ return 0
+ fi
+ done
+ let error_count=0
+ if [[ $run_status -ne 0 ]]
+ then
+ echo "ERROR: $main failed! ($out)"
+ let error_count+=1
+ fi
+ count_problem "$main" "$out"
+ let error_count+=$?
+ # $VERBOSE && cat "$out"
+ return $error_count
+}
+
+export total_problem_count
+let total_problem_count=0
+
+check() {
+ main="$1"
+ shift
+ out="$out_root/$main.$out_ext"
+ $VERBOSE && echo "$main --> $out"
+ if ! $CHECK_ONLY
+ then
+ $NOOP rm -f "$out"
+ if [[ -z "$NOOP" ]]
+ then
+ mkdir -p $(dirname "$out")
+ TERM=dumb sbt "runMain $main $mains_args[\"$main\"] $@" > "$out"
+ else
+ $NOOP mkdir -p $(dirname $out)
+ $NOOP "TERM=dumb sbt runMain $main $mains_args[\"$main\"] $@ > $out"
+ fi
+ fi
+ $NOOP report $? "$main" "$out"
+ let total_problem_count+=$?
+ # return $?
+}
+
+problem_count="$out_root/mains-problem-count.txt" # see "hack" note below.
+rm -f "$problem_count"
+for main in "${mains[@]}"
+do
+ check $main
+ # hack! The value of total_problem_count is lost to the outer shell,
+ # so write the values to a file for consumption "outside".
+ echo $total_problem_count >> "$problem_count"
+done
+
+if [[ -f "$problem_count" ]]
+then
+ let total_problem_count=$(tail -n 1 "$problem_count")
+ rm -f "$problem_count"
+ if [[ $total_problem_count -gt 0 ]]
+ then
+ echo "ERROR: $total_problem_count issues found. See $error_log"
+ print_count $total_problem_count $error_log "" "issues found!"
+ exit 1
+ fi
+fi
+echo "No obvious issues found, but consider checking all the output files in $out_root!"
+exit 0
+
diff --git a/check-scripts.sh b/check-scripts.sh
index af6066dc..92ed5ebd 100755
--- a/check-scripts.sh
+++ b/check-scripts.sh
@@ -1,8 +1,57 @@
-#!/usr/bin/env bash
+#!/usr/bin/env zsh
default_dirs=( "src/script/scala" )
out_root="target/script-tests"
out_ext="out"
+timestamp=$(date +"%Y-%m-%d_%H-%M-%S")
+error_log="$out_root/scripts-errors-$timestamp.log"
+expected_errors_in=(
+ src/script/scala/progscala3/IndentationSyntax.scala
+ src/script/scala/progscala3/appdesign/Deprecated.scala
+ src/script/scala/progscala3/basicoop/DollarsPercentagesOpaque.scala
+ src/script/scala/progscala3/basicoop/GoodBad.scala
+ src/script/scala/progscala3/basicoop/MatchableOpaque.scala
+ src/script/scala/progscala3/basicoop/tagging/Tags.scala
+ src/script/scala/progscala3/basicoop/tagging/Tags2.scala
+ src/script/scala/progscala3/collections/MultiMap.scala
+ src/script/scala/progscala3/contexts/ExtensionMethodScoping.scala
+ src/script/scala/progscala3/contexts/ImplicitEvidence.scala
+ src/script/scala/progscala3/contexts/ImplicitNotFound.scala
+ src/script/scala/progscala3/contexts/MatchGivens.scala
+ src/script/scala/progscala3/contexts/SeqUnzip.scala
+ src/script/scala/progscala3/dynamic/SelectableSQL.scala
+ src/script/scala/progscala3/meta/compiletime/RequireConst.scala
+ src/script/scala/progscala3/meta/compiletime/SummonAll.scala
+ src/script/scala/progscala3/meta/inline/ConditionalMatch.scala
+ src/script/scala/progscala3/meta/inline/Overrides.scala
+ src/script/scala/progscala3/meta/inline/Recursive.scala
+ src/script/scala/progscala3/objectsystem/variance/MutableVariance.scala
+ src/script/scala/progscala3/patternmatching/Matchable.scala
+ src/script/scala/progscala3/patternmatching/MatchExhaustive.scala
+ src/script/scala/progscala3/patternmatching/MatchForFiltering.scala
+ src/script/scala/progscala3/patternmatching/MatchSurprise.scala
+ src/script/scala/progscala3/patternmatching/MatchTypesErasure.scala
+ src/script/scala/progscala3/patternmatching/UnapplySingleValue2.scala
+ src/script/scala/progscala3/rounding/InfixMethod.scala
+ src/script/scala/progscala3/rounding/InfixType.scala
+ src/script/scala/progscala3/rounding/TypeErasureProblem.scala
+ src/script/scala/progscala3/typelessdomore/FibonacciTailrec.scala
+ src/script/scala/progscala3/typelessdomore/Human.scala
+ src/script/scala/progscala3/typelessdomore/MethodBroadInference.scala
+ src/script/scala/progscala3/typelessdomore/MethodNestedReturn.scala
+ src/script/scala/progscala3/typelessdomore/MethodRecursiveReturn.scala
+ src/script/scala/progscala3/typelessdomore/RepeatedParameters.scala
+ src/script/scala/progscala3/typesystem/bounds/ViewToContextBounds.scala
+ src/script/scala/progscala3/typesystem/deptypes/DependentTypes.scala
+ src/script/scala/progscala3/typesystem/deptypes/DependentTypesBounds.scala
+ src/script/scala/progscala3/typesystem/deptypes/DependentTypesSimple.scala
+ src/script/scala/progscala3/typesystem/intersectionunion/Intersection.scala
+ src/script/scala/progscala3/typesystem/intersectionunion/Union.scala
+ src/script/scala/progscala3/typesystem/matchtypes/MatchTypes2.scala
+ src/script/scala/progscala3/typesystem/typepaths/TypePath.scala
+ src/script/scala/progscala3/typesystem/valuetypes/SingletonTypes.scala
+ src/script/scala/progscala3/typesystem/valuetypes/TypeProjection.scala
+)
error() {
echo "ERROR: $@"
@@ -21,26 +70,49 @@ are files with @main methods under src/main that can be interpreted as Scala 3
argument.
So, this bash script starts the REPL (using "sbt console") for each file and then
-uses :load to load the file. The output is written to
+uses :load to load the file. The output for that console sessions is written to
$out_root/path/to/file.$out_ext.
-Some files DO throw errors. In some cases, you'll see a comment on the same line
-like "// ERROR". In other cases, you have to look at the book discussion to see
-if the error is expected. Unfortunately, all the output has to be inspected manually.
-If you see lots of errors for any one file, make sure you are using a Scala 3 REPL!
+A list of files with errors or warnings is written to $error_log.
+
+The following files are known to throw errors intentionally:
+$(for f in ${expected_errors_in[@]}; do echo " $f"; done)
+
+Failures for these known files are ignored, but logged in $error_log.
+In most of them, you'll see a comment on the same line, like "// ERROR" or "// COMPILATION ERROR",
+which are easier to spot when looking at error messages. In the rest of the cases, you have to
+look at the book discussion to see if the error is expected. Unfortunately, this means that any
+unexpected errors in these files will be missed, unless you inspect the output carefully!
+
+For finding unexpected errors, the console output is searched for errors by looking
+for any of the following lines near the end (where N=2+):
+
+1 warning found
+N warnings found
+1 error found
+N errors found
+
+
+** HOWEVER, to be really safe, all the outputs should still be inspected manually. **
Usage: $0 [-h|--help] [-v|--verbose] [-c|--clean] [-n|--no-exec] [dir ...]
Where:
-h | --help Print this message and exit.
--v | --verbose Print each file name to the console as it is processed.
+-v | --verbose Print each file name to the console as it is processed and dump
+ to stdout the test output (in the script's corresponding
+ "$out_root/...").
-c | --clean Delete all previous output.
-n | --no-exec Don't execute the commands, just echo what would be done.
+--check | --check-only
+ Don't run the scripts; just check for reported errors only
+ on any existing output files under $out_root.
dir ... Start in these directories. (default "${default_dirs[@]}")
EOF
}
: ${VERBOSE:=false}
: ${CLEAN:=false}
+: ${CHECK_ONLY=false}
: ${NOOP:=}
dirs=()
@@ -54,7 +126,10 @@ do
-v|--v*)
VERBOSE=true
;;
- -c|--c*)
+ --check*)
+ CHECK_ONLY=true
+ ;;
+ -c|--cl*)
CLEAN=true
;;
-n|--n*)
@@ -71,7 +146,7 @@ do
done
[[ ${#dirs[@]} -gt 0 ]] || dirs=( ${default_dirs[@]} )
-$VERBOSE && echo "Reading directories ${dirs[@]}"
+$VERBOSE && echo "Reading directories: ${dirs[@]}"
if $CLEAN
then
@@ -79,27 +154,98 @@ then
[[ -n "$out_root" ]] && rm -rf "$out_root" # safety check!
fi
+rm -f $error_log
+
+print_count() {
+ let count=$1; shift
+ file=$1; shift
+ out=$1; shift
+ message="$1"; shift
+ printf '%5d: %s %s %s\n' $count "$file" "$out" "$message" >> $error_log
+}
+
+count_problem() {
+ script=$1
+ out=$2
+ let count=$(grep -cE "^.+ (error|warning)s? found$" "$out")
+ [[ $count -gt 0 ]] && print_count $count $script $out
+ return $count
+}
+
+report() {
+ let run_status=$1
+ script=$2
+ out=$3
+ for skip in ${expected_errors_in[@]}
+ do
+ if [[ "$skip" = "$script" ]]
+ then
+ print_count 0 "$script" "$out" "NOTE: because of known deliberate errors, unexpected errors might be missed!"
+ return 0
+ fi
+ done
+ let error_count=0
+ if [[ $run_status -ne 0 ]]
+ then
+ echo "ERROR: $script failed! ($out)"
+ let error_count+=1
+ fi
+ count_problem "$script" "$out"
+ let error_count+=$?
+ # $VERBOSE && cat "$out"
+ return $error_count
+}
+
+export total_problem_count
+let total_problem_count=0
+
check() {
script="$1"
out="$out_root/$script.$out_ext"
- $NOOP rm -f $out
- $VERBOSE && echo "$f --> $out"
- if [[ -z "$NOOP" ]]
+ $VERBOSE && echo "$script --> $out"
+ if ! $CHECK_ONLY
then
- mkdir -p $(dirname $out)
- TERM=dumb sbt console < $out
+ $NOOP rm -f "$out"
+ if [[ -z "$NOOP" ]]
+ then
+ mkdir -p $(dirname "$out")
+ TERM=dumb sbt console < "$out"
:load $script
EOF
- else
- $NOOP mkdir -p $(dirname $out)
- $NOOP "TERM=dumb sbt console ... :load $script ... > $out"
+ else
+ $NOOP mkdir -p $(dirname $out)
+ $NOOP "TERM=dumb sbt console ... :load $script ... > $out"
+ fi
fi
+ $NOOP report $? "$script" "$out"
+ let total_problem_count+=$?
+ # return $?
}
+problem_count="$out_root/scripts-problem-count.txt" # see "hack" note below.
+rm -f "$problem_count"
for dir in "${dirs[@]}"
do
find "$dir" -name '*.scala' | while read f
do
check $f
+ # hack! The value of total_problem_count is lost to the outer shell,
+ # so write the values to a file for consumption "outside".
+ echo $total_problem_count >> "$problem_count"
done
done
+
+if [[ -f "$problem_count" ]]
+then
+ let total_problem_count=$(tail -n 1 "$problem_count")
+ rm -f "$problem_count"
+ if [[ $total_problem_count -gt 0 ]]
+ then
+ echo "ERROR: $total_problem_count issues found. See $error_log"
+ print_count $total_problem_count $error_log "" "issues found!"
+ exit 1
+ fi
+fi
+echo "No obvious issues found, but consider checking all the output files in $out_root!"
+exit 0
+
diff --git a/project/build.properties b/project/build.properties
index 09feeeed..6edd024b 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version=1.10.4
+sbt.version=1.12.10
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 28d7630d..3a86dc67 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -3,4 +3,4 @@ resolvers ++= Seq(
"Sonatype snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
)
-addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.2.2")
+addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.4.4")
diff --git a/src/main/scala/progscala3/appdesign/dbc/Elidable.scala b/src/main/scala/progscala3/appdesign/dbc/Elidable.scala
index 82000a86..ea1745d4 100644
--- a/src/main/scala/progscala3/appdesign/dbc/Elidable.scala
+++ b/src/main/scala/progscala3/appdesign/dbc/Elidable.scala
@@ -5,6 +5,8 @@ import scala.annotation.elidable
import scala.annotation.elidable.*
/**
+ * Update for Scala 3.8. @elidable is now deprecated!
+ *
* This example of the elidable annotation is mentioned in the book, but not shown.
* Compile outside sbt using the scala compiler:
* ```
@@ -17,11 +19,11 @@ import scala.annotation.elidable.*
* in Scala 3, but it may be added in a subsequent release.
.*/
object MyLogger:
- @elidable(WARNING)
+ // @elidable(WARNING)
def warn(message: String) = println(s"WARNING: $message")
- @elidable(INFO)
+ // @elidable(INFO)
def info(message: String) = println(s"INFO: $message")
- @elidable(ASSERTION)
+ // @elidable(ASSERTION)
def assertion(message: String) = println(s"ASSERTION: $message")
@main def TryMyLogger =
diff --git a/src/main/scala/progscala3/appdesign/parthenon/PayrollUseCases.scala b/src/main/scala/progscala3/appdesign/parthenon/PayrollUseCases.scala
index a91fa0b3..028a8f30 100644
--- a/src/main/scala/progscala3/appdesign/parthenon/PayrollUseCases.scala
+++ b/src/main/scala/progscala3/appdesign/parthenon/PayrollUseCases.scala
@@ -32,7 +32,7 @@ object PayrollUseCases:
val files =
if inputFileNames.length == 0 then Seq("misc/parthenon-payroll.txt")
else inputFileNames
- for (file <- files) do
+ for file <- files do
println(s"Processing input file: $file")
val data = fromFile(file)
biweeklyPayrollPerEmployee(data)
diff --git a/src/main/scala/progscala3/basicoop/NoSQLRecordsRevisited.scala b/src/main/scala/progscala3/basicoop/NoSQLRecordsRevisited.scala
index 14d383e5..1c2e9d84 100644
--- a/src/main/scala/progscala3/basicoop/NoSQLRecordsRevisited.scala
+++ b/src/main/scala/progscala3/basicoop/NoSQLRecordsRevisited.scala
@@ -21,11 +21,11 @@ extension (rec: Record)
object Record:
def empty: Record = Map.empty
-given FromTo[Int] with
+given FromTo[Int]:
def apply(any: Any): Int = any.asInstanceOf[Int]
-given FromTo[Double] with
+given FromTo[Double]:
def apply(any: Any): Double = any.asInstanceOf[Double]
-given FromTo[String] with
+given FromTo[String]:
def apply(any: Any): String = any.asInstanceOf[String]
@main def TryScalaDBRevisited =
diff --git a/src/main/scala/progscala3/basicoop/tagging/Tags.scala b/src/main/scala/progscala3/basicoop/tagging/Tags.scala
index 1fc8835e..68965234 100644
--- a/src/main/scala/progscala3/basicoop/tagging/Tags.scala
+++ b/src/main/scala/progscala3/basicoop/tagging/Tags.scala
@@ -57,7 +57,8 @@ end Tagging
// Compilation Errors!
// om.compare(x, y)
val expected: Double @@ Meter = 1.0.tag
- assert(xs.min(om) == expected)
+ // 2025-06-16: In Scala 3.7, "using" is required in the next line:
+ assert(xs.min(using om) == expected)
// Compilation Error!
// xs.min(o)
end TryTagging
diff --git a/src/main/scala/progscala3/basicoop/tagging/Tags2.scala b/src/main/scala/progscala3/basicoop/tagging/Tags2.scala
index f30c72bb..b5e5a8f3 100644
--- a/src/main/scala/progscala3/basicoop/tagging/Tags2.scala
+++ b/src/main/scala/progscala3/basicoop/tagging/Tags2.scala
@@ -21,11 +21,11 @@ object Tagging2:
@targetName("Tag") type @@[S, T] = Tagged[S, T]
/**
+ * Pre Scala 3.7.X:
* Instead of extension methods, use implicit conversions to value classes,
* which won't add any runtime overhead, but allow us to use methods like `tag`
* the way we want. This implementation is closer to the original example in
* SIP-35.
- */
implicit class tagOps[S](s: S):
def tag[T]: S @@ T = Tagged.tag(s)
implicit class untagOps[S, T](st: S @@ T):
@@ -34,6 +34,23 @@ object Tagging2:
def tags[T]: F[S @@ T] = Tagged.tags(fs)
implicit class untagsOps[F[_], S, T](fst: F[S @@ T]):
def untags: F[S] = Tagged.untags(fst)
+ */
+
+ /**
+ * DeanW (September 2025): as of Scala 3.7.X, implicit classes are no longer
+ * supported. Alternative are extension methods and declaring regular classes
+ * and using given conversions to them. Here, we go back to using extension methods!
+ */
+
+ extension [S](s: S)
+ def tag[T]: S @@ T = Tagged.tag(s)
+ extension [S, T](st: S @@ T)
+ def untag: S = Tagged.untag(st)
+ extension [F[_], S](fs: F[S])
+ def tags[T]: F[S @@ T] = Tagged.tags(fs)
+ extension [F[_], S, T](fst: F[S @@ T])
+ def untags: F[S] = Tagged.untags(fst)
+
end Tagging2
@main def TryTagging2(): Unit =
@@ -60,7 +77,8 @@ end Tagging2
// Compilation Errors!
// om.compare(x, y)
// x == y
- assert(xs.min(om) == 1.0.tag[Meter])
+ // 2025-06-16: In Scala 3.7, "using" is required in the next line:
+ assert(xs.min(using om) == 1.0.tag[Meter])
// Compilation Error!
// xs.min(o)
end TryTagging2
diff --git a/src/main/scala/progscala3/concurrency/futures/FutureForComp.scala b/src/main/scala/progscala3/concurrency/futures/FutureForComp.scala
index c3432c84..ce79b19e 100644
--- a/src/main/scala/progscala3/concurrency/futures/FutureForComp.scala
+++ b/src/main/scala/progscala3/concurrency/futures/FutureForComp.scala
@@ -18,8 +18,8 @@ def make(i: Int): Future[String] =
else Future.failed(ThatsOdd(i))
@main def TryFuturesForComp =
- val futures = for {
+ val futures = for
i <- (0 to 9)
future = make(i)
- } yield future
+ yield future
futures.map(_.onComplete(doComplete))
diff --git a/src/main/scala/progscala3/contexts/NoSQLRecords.scala b/src/main/scala/progscala3/contexts/NoSQLRecords.scala
index 261ed45f..e98c6327 100644
--- a/src/main/scala/progscala3/contexts/NoSQLRecords.scala
+++ b/src/main/scala/progscala3/contexts/NoSQLRecords.scala
@@ -28,7 +28,7 @@ case class Record private (contents: Map[String,Any]): // <2>
given Conv[Int] = _.asInstanceOf[Int] // <5>
given Conv[Double] = _.asInstanceOf[Double]
given Conv[String] = _.asInstanceOf[String]
- given ab[A : Conv, B : Conv]: Conv[(A, B)] = _.asInstanceOf[(A,B)]
+ given ab: [A : Conv, B : Conv] => Conv[(A, B)] = _.asInstanceOf[(A,B)]
val rec = Record.make.add("one" -> 1).add("two" -> 2.2)
.add("three" -> "THREE!").add("four" -> (4.4, "four"))
diff --git a/src/main/scala/progscala3/contexts/accounting/NewImplicitConversions.scala b/src/main/scala/progscala3/contexts/accounting/NewImplicitConversions.scala
index 9b8d31fb..20e03d89 100644
--- a/src/main/scala/progscala3/contexts/accounting/NewImplicitConversions.scala
+++ b/src/main/scala/progscala3/contexts/accounting/NewImplicitConversions.scala
@@ -42,7 +42,7 @@ case class Salary(gross: Dollars, taxes: Percentage):
val salary = Salary(100_000.0, 20.0)
println(s"salary: $salary. Net pay: ${salary.net}")
- given Conversion[Int,Dollars] with // <3>
+ given Conversion[Int,Dollars]: // <3>
def apply(i:Int): Dollars= Dollars(i.toDouble)
val dollars: Dollars = 10 // <4>
diff --git a/src/main/scala/progscala3/contexts/json/JSONBuilder.scala b/src/main/scala/progscala3/contexts/json/JSONBuilder.scala
index 67ecff10..89f3722e 100644
--- a/src/main/scala/progscala3/contexts/json/JSONBuilder.scala
+++ b/src/main/scala/progscala3/contexts/json/JSONBuilder.scala
@@ -102,7 +102,7 @@ object JSONBuilder:
* are _witnesses_, constraining the allowed types of JSON values. Note that
* there is nothing to implement in the trait, but we have to use the `with {}`
* clauses to make these definitions concrete.
- * NOTE: Scala 3.0.0 requires "given ValidJSONValue[Int] with {}", while 3.0.1
+ * NOTE: Scala 3.0.0 requires "given ValidJSONValue[Int]: {}", while 3.0.1
* removed the need for "with {}", but you have to add the "()".
*/
sealed trait ValidJSONValue[T <: Matchable]
diff --git a/src/main/scala/progscala3/contexts/typeclass/MonoidTypeClass.scala b/src/main/scala/progscala3/contexts/typeclass/MonoidTypeClass.scala
index 2abde30b..4e391c82 100644
--- a/src/main/scala/progscala3/contexts/typeclass/MonoidTypeClass.scala
+++ b/src/main/scala/progscala3/contexts/typeclass/MonoidTypeClass.scala
@@ -11,11 +11,11 @@ trait Semigroup[T]:
trait Monoid[T] extends Semigroup[T]:
def unit: T // <2>
-given StringMonoid: Monoid[String] with // <3>
+given StringMonoid: Monoid[String]: // <3>
def unit: String = ""
extension (s: String) infix def combine(other: String): String = s + other
-given IntMonoid: Monoid[Int] with
+given IntMonoid: Monoid[Int]:
def unit: Int = 0
extension (i: Int) infix def combine(other: Int): Int = i + other
// end::definitions[]
diff --git a/src/main/scala/progscala3/contexts/typeclass/UsingClauses.scala b/src/main/scala/progscala3/contexts/typeclass/UsingClauses.scala
new file mode 100644
index 00000000..26fa033d
--- /dev/null
+++ b/src/main/scala/progscala3/contexts/typeclass/UsingClauses.scala
@@ -0,0 +1,71 @@
+// tag::definitions[]
+// src/main/scala/progscala3/contexts/UsingClauses.scala
+// This is identical to src/script/scala/progscala3/contexts/UsingClauses.scala,
+// with the method invocations moved to a new @main function at the end.
+// I created it just to make it easier to test some work, and decided it
+// "doesn't hurt" to keep it around, but only the script version is used in the
+// book.
+
+case class SortableSeq[A](seq: Seq[A]):
+ def sortBy1a[B](transform: A => B)(using o: Ordering[B]): SortableSeq[A] =
+ SortableSeq(seq.sortBy(transform)(using o))
+
+ def sortBy1b[B](transform: A => B)(using Ordering[B]): SortableSeq[A] =
+ SortableSeq(seq.sortBy(transform)(using summon[Ordering[B]]))
+
+ def sortBy2[B : Ordering](transform: A => B): SortableSeq[A] =
+ SortableSeq(seq.sortBy(transform)(using summon[Ordering[B]]))
+// end::definitions[]
+
+// tag::defaultOrdering[]
+def defaultOrdering() =
+ val seq = SortableSeq(Seq(1,3,5,2,4))
+ val expected = SortableSeq(Seq(5, 4, 3, 2, 1))
+ assert(seq.sortBy1a(i => -i) == expected)
+ assert(seq.sortBy1b(i => -i) == expected)
+ assert(seq.sortBy2(i => -i) == expected)
+
+// end::defaultOrdering[]
+
+// tag::oddEvenImplicitOrdering[]
+def oddEvenImplicitOrdering() =
+ implicit val oddEven: Ordering[Int] = new Ordering[Int]:
+ def compare(i: Int, j: Int): Int = i%2 compare j%2 match
+ case 0 => i compare j
+ case c => c
+
+ val seq = SortableSeq(Seq(1,3,5,2,4))
+ val expected = SortableSeq(Seq(5, 3, 1, 4, 2))
+ assert(seq.sortBy1a(i => -i) == expected)
+ assert(seq.sortBy1b(i => -i) == expected)
+ assert(seq.sortBy2(i => -i) == expected)
+
+ assert(seq.sortBy1a(i => -i)(using oddEven) == expected)
+ assert(seq.sortBy1b(i => -i)(using oddEven) == expected)
+ assert(seq.sortBy2(i => -i)(using oddEven) == expected)
+
+// end::oddEvenImplicitOrdering[]
+
+// tag::oddEvenGivenOrdering[]
+def evenOddGivenOrdering() =
+ given evenOdd: Ordering[Int]:
+ def compare(i: Int, j: Int): Int = i%2 compare j%2 match
+ case 0 => i compare j
+ case c => -c
+
+ val seq = SortableSeq(Seq(1,3,5,2,4))
+ val expected = SortableSeq(Seq(4, 2, 5, 3, 1))
+ assert(seq.sortBy1a(i => -i) == expected) // <1>
+ assert(seq.sortBy1b(i => -i) == expected)
+ assert(seq.sortBy2(i => -i) == expected)
+
+ assert(seq.sortBy1a(i => -i)(using evenOdd) == expected) // <2>
+ assert(seq.sortBy1b(i => -i)(using evenOdd) == expected)
+ assert(seq.sortBy2(i => -i)(using evenOdd) == expected)
+
+// end::oddEvenGivenOrdering[]
+
+@main def checkUsingClauses() =
+ defaultOrdering()
+ oddEvenImplicitOrdering()
+ evenOddGivenOrdering()
diff --git a/src/main/scala/progscala3/contexts/typeclass/new1/ToJSONTypeClasses.scala b/src/main/scala/progscala3/contexts/typeclass/new1/ToJSONTypeClasses.scala
index 5df5255d..a8486841 100644
--- a/src/main/scala/progscala3/contexts/typeclass/new1/ToJSONTypeClasses.scala
+++ b/src/main/scala/progscala3/contexts/typeclass/new1/ToJSONTypeClasses.scala
@@ -5,7 +5,7 @@ package progscala3.contexts.typeclass.new1
import progscala3.introscala.shapes.{Point, Shape, Circle, Rectangle, Triangle}
import progscala3.contexts.json.ToJSON
-given ToJSON[Point] with // <1>
+given ToJSON[Point]: // <1>
extension (point: Point)
def toJSON(name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
@@ -14,7 +14,7 @@ given ToJSON[Point] with // <1>
|${indent}"y": "${point.y}"
|$outdent}""".stripMargin
-given ToJSON[Circle] with // <2>
+given ToJSON[Circle]: // <2>
extension (circle: Circle)
def toJSON(name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
@@ -25,7 +25,7 @@ given ToJSON[Circle] with // <2>
// end::definitions1[]
// tag::definitions2[]
-given ToJSON[Rectangle] with
+given ToJSON[Rectangle]:
extension (rect: Rectangle)
def toJSON(name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
@@ -35,7 +35,7 @@ given ToJSON[Rectangle] with
|${indent}"width": ${rect.width}
|$outdent}""".stripMargin
-given ToJSON[Triangle] with
+given ToJSON[Triangle]:
extension (tri: Triangle)
def toJSON(name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
diff --git a/src/main/scala/progscala3/contexts/typeclass/new2/ToJSONTypeClasses.scala b/src/main/scala/progscala3/contexts/typeclass/new2/ToJSONTypeClasses.scala
index 1174cc33..969ecc4a 100644
--- a/src/main/scala/progscala3/contexts/typeclass/new2/ToJSONTypeClasses.scala
+++ b/src/main/scala/progscala3/contexts/typeclass/new2/ToJSONTypeClasses.scala
@@ -4,7 +4,7 @@ package progscala3.contexts.typeclass.new2
import progscala3.introscala.shapes.{Point, Shape, Circle, Rectangle, Triangle}
import progscala3.contexts.json.ToJSON
-given ToJSON[Point] with
+given ToJSON[Point]:
extension (point: Point)
def toJSON(name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
@@ -13,7 +13,7 @@ given ToJSON[Point] with
|${indent}"y": "${point.y}"
|$outdent}""".stripMargin
-given ToJSON[Circle] with
+given ToJSON[Circle]:
extension (circle: Circle)
def toJSON(name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
@@ -22,7 +22,7 @@ given ToJSON[Circle] with
|${indent}"radius": ${circle.radius}
|$outdent}""".stripMargin
-given ToJSON[Rectangle] with
+given ToJSON[Rectangle]:
extension (rect: Rectangle)
def toJSON(name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
@@ -32,7 +32,7 @@ given ToJSON[Rectangle] with
|${indent}"width": ${rect.width}
|$outdent}""".stripMargin
-given ToJSON[Triangle] with
+given ToJSON[Triangle]:
extension (tri: Triangle)
def toJSON(name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
@@ -45,7 +45,7 @@ given ToJSON[Triangle] with
// tag::ToJSONShape[]
// src/main/scala/progscala3/contexts/typeclass/new2/ToJSONTypeClasses.scala
-given ToJSON[Shape] with
+given ToJSON[Shape]:
extension (shape: Shape)
def toJSON(name: String = "", level: Int = 0): String =
shape match
diff --git a/src/main/scala/progscala3/contexts/typeclass/new3/ToJSONTypeClasses.scala b/src/main/scala/progscala3/contexts/typeclass/new3/ToJSONTypeClasses.scala
index da07b129..d75f9451 100644
--- a/src/main/scala/progscala3/contexts/typeclass/new3/ToJSONTypeClasses.scala
+++ b/src/main/scala/progscala3/contexts/typeclass/new3/ToJSONTypeClasses.scala
@@ -4,7 +4,7 @@ package progscala3.contexts.typeclass.new3
import progscala3.introscala.shapes.{Point, Shape, Circle, Rectangle, Triangle}
import progscala3.contexts.json.ToJSON
-given ToJSON[Point] with
+given ToJSON[Point]:
extension (point: Point)
def toJSON(name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
@@ -13,7 +13,7 @@ given ToJSON[Point] with
|${indent}"y": "${point.y}"
|$outdent}""".stripMargin
-given circleToJSON: ToJSON[Circle] with
+given circleToJSON: ToJSON[Circle]:
def toJSON2(circle: Circle, name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
s"""${handleName(name)}{
@@ -24,7 +24,7 @@ given circleToJSON: ToJSON[Circle] with
def toJSON(name: String = "", level: Int = 0): String =
toJSON2(circle, name, level)
-given rectangleToJSON: ToJSON[Rectangle] with
+given rectangleToJSON: ToJSON[Rectangle]:
def toJSON2(rect: Rectangle, name: String = "", level: Int = 0): String =
val (outdent, indent) = indentation(level)
s"""${handleName(name)}{
@@ -39,7 +39,7 @@ given rectangleToJSON: ToJSON[Rectangle] with
// tag::ToJSONShape[]
// src/main/scala/progscala3/contexts/typeclass/new3/ToJSONTypeClasses.scala
-given triangleToJSON: ToJSON[Triangle] with // <1>
+given triangleToJSON: ToJSON[Triangle]: // <1>
def toJSON2(
tri: Triangle, name: String = "", level: Int = 0): String = // <2>
val (outdent, indent) = indentation(level)
@@ -52,7 +52,7 @@ given triangleToJSON: ToJSON[Triangle] with // <1>
def toJSON(name: String = "", level: Int = 0): String =
toJSON2(tri, name, level) // <3>
-given ToJSON[Shape] with
+given ToJSON[Shape]:
extension (shape: Shape)
def toJSON(name: String = "", level: Int = 0): String =
shape match
diff --git a/src/main/scala/progscala3/contexts/typeclass/new4/ToJSONTypeClasses.scala b/src/main/scala/progscala3/contexts/typeclass/new4/ToJSONTypeClasses.scala
index 7ac5265c..a62ccd5b 100644
--- a/src/main/scala/progscala3/contexts/typeclass/new4/ToJSONTypeClasses.scala
+++ b/src/main/scala/progscala3/contexts/typeclass/new4/ToJSONTypeClasses.scala
@@ -52,27 +52,27 @@ protected object ShapesToJSON:
|$outdent}""".stripMargin
end ShapesToJSON
-given pointToJSON: ToJSON[Point] with
+given pointToJSON: ToJSON[Point]:
extension (point: Point)
def toJSON(name: String = "", level: Int = 0): String =
ShapesToJSON(point, name, level)
-given circleToJSON: ToJSON[Circle] with
+given circleToJSON: ToJSON[Circle]:
extension (circle: Circle)
def toJSON(name: String = "", level: Int = 0): String =
ShapesToJSON(circle, name, level)
-given rectangleToJSON: ToJSON[Rectangle] with
+given rectangleToJSON: ToJSON[Rectangle]:
extension (rect: Rectangle)
def toJSON(name: String = "", level: Int = 0): String =
ShapesToJSON(rect, name, level)
-given triangleToJSON: ToJSON[Triangle] with
+given triangleToJSON: ToJSON[Triangle]:
extension (tri: Triangle)
def toJSON(name: String = "", level: Int = 0): String =
ShapesToJSON(tri, name, level)
-given shapeToJSON: ToJSON[Shape] with
+given shapeToJSON: ToJSON[Shape]:
extension (shape: Shape)
def toJSON(name: String = "", level: Int = 0): String = shape match
case c: Circle => ShapesToJSON(c, name, level)
diff --git a/src/main/scala/progscala3/contexts/typeclass/old/ToJSONTypeClasses.scala b/src/main/scala/progscala3/contexts/typeclass/old/ToJSONTypeClasses.scala
index 4dc7e862..c47c52da 100644
--- a/src/main/scala/progscala3/contexts/typeclass/old/ToJSONTypeClasses.scala
+++ b/src/main/scala/progscala3/contexts/typeclass/old/ToJSONTypeClasses.scala
@@ -4,51 +4,47 @@ package progscala3.contexts.typeclass.old
import progscala3.introscala.shapes.{Point, Shape, Circle, Rectangle, Triangle}
-trait ToJSONOld[T]:
- def toJSON(name: String = "", level: Int = 0): String // <1>
+// DeanW: September 14, 2025. Scala is dropping support for implicit classes, so the
+// `implicit final class PointToJSON`, etc. are now replaced by extension methods
+// and the trait ToJSONOld is converted to an object for its methods.
- protected val indent = " "
- protected def indentation(level: Int): (String,String) =
+object ToJSONOld:
+ val indent = " "
+ def indentation(level: Int): (String,String) =
(indent * level, indent * (level+1))
- protected def handleName(name: String): String =
+ def handleName(name: String): String =
if name.length > 0 then s""""$name": """ else ""
-// end::trait[]
+end ToJSONOld
-// tag::pointcircle[]
-implicit final class PointToJSON(
- point: Point) extends ToJSONOld[Point]:
- def toJSON(name: String = "", level: Int = 0): String =
- val (outdent, indent) = indentation(level)
- s"""${handleName(name)}{
+extension(point: Point)
+ def toJSON(name: String, level: Int): String =
+ val (outdent, indent) = ToJSONOld.indentation(level)
+ s"""${ToJSONOld.handleName(name)}{
|${indent}"x": "${point.x}",
|${indent}"y": "${point.y}"
|$outdent}""".stripMargin
-implicit final class CircleToJSON(
- circle: Circle) extends ToJSONOld[Circle]:
- def toJSON(name: String = "", level: Int = 0): String =
- val (outdent, indent) = indentation(level)
- s"""${handleName(name)}{
+extension(circle: Circle)
+ def toJSON(name: String, level: Int): String =
+ val (outdent, indent) = ToJSONOld.indentation(level)
+ s"""${ToJSONOld.handleName(name)}{
|${indent}${circle.center.toJSON("center", level + 1)},
|${indent}"radius": ${circle.radius}
|$outdent}""".stripMargin
-// end::pointcircle[]
-implicit final class RectangleToJSON(
- rect: Rectangle) extends ToJSONOld[Rectangle]:
- def toJSON(name: String = "", level: Int = 0): String =
- val (outdent, indent) = indentation(level)
- s"""${handleName(name)}{
+extension(rect: Rectangle)
+ def toJSON(name: String, level: Int): String =
+ val (outdent, indent) = ToJSONOld.indentation(level)
+ s"""${ToJSONOld.handleName(name)}{
|${indent}${rect.lowerLeft.toJSON("lowerLeft", level + 1)},
|${indent}"height": ${rect.height}
|${indent}"width": ${rect.width}
|$outdent}""".stripMargin
-implicit final class TriangleToJSON(
- tri: Triangle) extends ToJSONOld[Triangle]:
- def toJSON(name: String = "", level: Int = 0): String =
- val (outdent, indent) = indentation(level)
- s"""${handleName(name)}{
+extension(tri: Triangle)
+ def toJSON(name: String, level: Int): String =
+ val (outdent, indent) = ToJSONOld.indentation(level)
+ s"""${ToJSONOld.handleName(name)}{
|${indent}${tri.point1.toJSON("point1", level + 1)},
|${indent}${tri.point2.toJSON("point2", level + 1)},
|${indent}${tri.point3.toJSON("point3", level + 1)},
diff --git a/src/main/scala/progscala3/dsls/payroll/Money.scala b/src/main/scala/progscala3/dsls/payroll/Money.scala
index 3e2bdceb..24d2d916 100644
--- a/src/main/scala/progscala3/dsls/payroll/Money.scala
+++ b/src/main/scala/progscala3/dsls/payroll/Money.scala
@@ -3,16 +3,27 @@ package progscala3.dsls.payroll
import progscala3.contexts.accounting.* // <1>
import scala.util.FromDigits.Floating // <2>
-given Floating[Dollars] with // <3>
+given Floating[Dollars]: // <3>
def fromDigits(digits: String): Dollars = Dollars(digits.toDouble)
-given Floating[Percentage] with
+given Floating[Percentage]:
def fromDigits(digits: String): Percentage = Percentage(digits.toDouble)
-implicit class dsc(sc: StringContext): // <4>
+/**
+ * DeanW (September 2025): as of Scala 3.7.X, implicit classes are no longer
+ * supported. Alternative are extension methods and declaring regular classes
+ * with given conversions. Here, we'll use the latter:
+implicit class dsc(sc: StringContext):
def $(tokens: Any*) =
val str = StringContextUtil.foldTokens(tokens.toSeq, sc.parts)
Dollars(str.toDouble)
+ */
+
+class dsc(sc: StringContext): // <4>
+ def $(tokens: Any*) =
+ val str = StringContextUtil.foldTokens(tokens.toSeq, sc.parts)
+ Dollars(str.toDouble)
+given Conversion[StringContext, dsc] = sc => dsc(sc)
extension (amount: Double) // <5>
def dollars: Dollars = Dollars(amount)
diff --git a/src/main/scala/progscala3/fp/categories/Functor2.scala b/src/main/scala/progscala3/fp/categories/Functor2.scala
index 6d9a49bc..2c6ac90d 100644
--- a/src/main/scala/progscala3/fp/categories/Functor2.scala
+++ b/src/main/scala/progscala3/fp/categories/Functor2.scala
@@ -106,9 +106,9 @@ object FunctionF2B:
(fa: F[A]) => flatMap(fa)(t compose f)
@main def TryFunctionF2B() =
- given [A]: FunctionF2B.FlatMap[A,Seq] with
+ given [A] => FunctionF2B.FlatMap[A,Seq]:
def apply(seq: Seq[A])(f: A => Seq[A]): Seq[A] = seq.flatMap(f)
- given [A]: FunctionF2B.FlatMap[A,Option] with
+ given [A] => FunctionF2B.FlatMap[A,Option]:
def apply(seq: Option[A])(f: A => Option[A]): Option[A] = seq.flatMap(f)
val f: Int => Int = 2 * _
@@ -145,8 +145,8 @@ object FunctionF2C:
}
@main def TryFunctionF2C() =
- given [A]: FunctionF2C.Lift[A,Seq] = (a:A) => Seq(a)
- given [A]: FunctionF2B.FlatMap[A,Set] with
+ given [A] => FunctionF2C.Lift[A,Seq] = (a:A) => Seq(a)
+ given [A] => FunctionF2B.FlatMap[A,Set]:
def apply(set: Set[A])(f: A => Set[A]): Set[A] = set.flatMap(f)
val fseqd: Seq[Double] => Seq[Double] = _.map(2.0 * _)
@@ -183,8 +183,8 @@ object FunctionF2D:
}
@main def TryFunctionF2D() =
- given [A]: FunctionF2C.Lift[A,Seq] = (a:A) => Seq(a)
- given [A]: FunctionF2B.FlatMap[A,Set] with
+ given [A] => FunctionF2C.Lift[A,Seq] = (a:A) => Seq(a)
+ given [A] => FunctionF2B.FlatMap[A,Set]:
def apply(set: Set[A])(f: A => Set[A]): Set[A] = set.flatMap(f)
given Functor[Seq] = SeqF
diff --git a/src/main/scala/progscala3/fp/categories/MapMerge.scala b/src/main/scala/progscala3/fp/categories/MapMerge.scala
index ba3afa6c..37b0a678 100644
--- a/src/main/scala/progscala3/fp/categories/MapMerge.scala
+++ b/src/main/scala/progscala3/fp/categories/MapMerge.scala
@@ -2,7 +2,7 @@
package progscala3.fp.categories
import progscala3.contexts.typeclass.Monoid
-given MapMergeMonoid[K, V : Monoid]: Monoid[Map[K, V]] with // <1>
+given MapMergeMonoid: [K, V : Monoid] => Monoid[Map[K, V]]: // <1>
def unit: Map[K, V] = Map.empty
extension (map1: Map[K, V]) def combine(map2: Map[K, V]): Map[K, V] =
val kmon = summon[Monoid[V]]
diff --git a/src/main/scala/progscala3/introscala/shapes/ProcessShapesDriver.scala b/src/main/scala/progscala3/introscala/shapes/ProcessShapesDriver.scala
index d87c8a99..ae8fe8ba 100644
--- a/src/main/scala/progscala3/introscala/shapes/ProcessShapesDriver.scala
+++ b/src/main/scala/progscala3/introscala/shapes/ProcessShapesDriver.scala
@@ -5,7 +5,7 @@ package progscala3.introscala.shapes
val messages = Seq( // <2>
Draw(Circle(Point(0.0,0.0), 1.0)),
Draw(Rectangle(Point(0.0,0.0), 2, 5)),
- Response(s"Say hello to pi: 3.14159"),
+ Response(s"Say hello to pi: 3.14159 (Expected ERROR!)"),
Draw(Triangle(Point(0.0,0.0), Point(2.0,0.0), Point(1.0,2.0))),
Exit)
diff --git a/src/main/scala/progscala3/meta/TryInvariant.scala b/src/main/scala/progscala3/meta/TryInvariant.scala
index 1dbba65d..d3d99e32 100644
--- a/src/main/scala/progscala3/meta/TryInvariant.scala
+++ b/src/main/scala/progscala3/meta/TryInvariant.scala
@@ -6,4 +6,4 @@ package progscala3.meta
invariant(i >= 0, s"i = $i")(i += 1)
println(s"success: $i")
println(s"Will now fail:")
- invariant(i >= 0, s"i = $i")(i -= 2)
+ invariant(i >= 0, s"i = $i // expected ERROR")(i -= 2)
diff --git a/src/main/scala/progscala3/typesystem/intersectionunion/IntersectionUnion.scala b/src/main/scala/progscala3/typesystem/intersectionunion/IntersectionUnion.scala
index 37cebe19..99e2016c 100644
--- a/src/main/scala/progscala3/typesystem/intersectionunion/IntersectionUnion.scala
+++ b/src/main/scala/progscala3/typesystem/intersectionunion/IntersectionUnion.scala
@@ -135,7 +135,8 @@ object IntersectionUnion:
val seqT1T2T3s: Seq[T1 | T2 | T3] = Seq(new T1 {}, new T2 {}, new T3 {})
seqT1T2T3s.map(fT1T2T31)
- seqT1T2T3s.map(fT1T2T32)
+ // The Scala 3.8 type checker rejects the following!
+ // seqT1T2T3s.map(fT1T2T32)
seqT1T2T3s.map((x: AnyRef) => s"<$x>")
def main(args: Array[String]): Unit =
diff --git a/src/main/scala/progscala3/typesystem/typelambdas/Functor.scala b/src/main/scala/progscala3/typesystem/typelambdas/Functor.scala
index 271d0f3a..e393e3ab 100644
--- a/src/main/scala/progscala3/typesystem/typelambdas/Functor.scala
+++ b/src/main/scala/progscala3/typesystem/typelambdas/Functor.scala
@@ -5,12 +5,12 @@ trait Functor[M[_]]:
extension [A] (m: M[A]) def map2[B](f: A => B): M[B]
object Functor:
- given Functor[Seq] with
+ given Functor[Seq]:
extension [A] (seq: Seq[A]) def map2[B](f: A => B): Seq[B] = seq map f
type MapKV = [K] =>> [V] =>> Map[K,V] // <1>
- given [K]: Functor[MapKV[K]] with // <2>
+ given [K] => Functor[MapKV[K]]: // <2>
extension [V1] (map: MapKV[K][V1])
def map2[V2](f: V1 => V2): MapKV[K][V2] = map.view.mapValues(f).toMap
diff --git a/src/script/scala/progscala3/BracesSyntax.scala b/src/script/scala/progscala3/BracesSyntax.scala
index 1963d6ac..dadca2cd 100644
--- a/src/script/scala/progscala3/BracesSyntax.scala
+++ b/src/script/scala/progscala3/BracesSyntax.scala
@@ -39,9 +39,10 @@ var i = 0
while (i < 10) { i+=1 }
// Match expression
-0 match {
+var x: Int = 0
+x match {
case 0 => println("zero")
- case _ => println("other value")
+ case _ => println("other value")
}
// Partially-defined function
@@ -50,18 +51,17 @@ val opt: Option[Int] => Int = {
case None => 0
}
-// Try, catch, finally expression
-import scala.io.Source
+// Try, catch, finally expression (contrived...)
import scala.util.control.NonFatal
-var source: Option[Source] = None
-try { // Can omit {} if nothing else is done with source...
- source = Some(Source.fromFile("README.md"))
- // do something with it
-} catch { // However, the {} are required for catch clauses
- case NonFatal(ex) => println(ex)
-} finally { // Can omit {} for both finally and if here.
- if (source != None) {
- source.get.close
+var string: Option[Int] = None
+try {
+ string = Some(Integer.parseInt("foo"))
+ println(s"Parsed \"foo\" to ${string}")
+} catch {
+ case NonFatal(ex) => println(s"NonFatal thrown: ${ex}")
+} finally {
+ if (string == None) {
+ println("string is None")
}
}
@@ -86,7 +86,7 @@ val mon = new Monoid[Int] {
}
// New type class given instantiation
-given intMonoid: Monoid[Float] with {
+given intMonoid: Monoid[Float] {
def add(f1: Float, f2: Float): Float = f1+f2
def zero: Float = 0.0F
}
diff --git a/src/script/scala/progscala3/IndentationSyntax.scala b/src/script/scala/progscala3/IndentationSyntax.scala
index add2f655..fb72b9fc 100644
--- a/src/script/scala/progscala3/IndentationSyntax.scala
+++ b/src/script/scala/progscala3/IndentationSyntax.scala
@@ -50,7 +50,8 @@ do // For longer do blocks.
end while
// Match expression
-0 match
+var x: Int = 0
+x match
case 0 => println("zero")
case _ => println("other value")
end match
@@ -61,18 +62,17 @@ val opt: Option[Int] => Int =
case None => 0
end opt
-// Try, catch, finally expression
-import scala.io.Source
+// Try, catch, finally expression (contrived...)
import scala.util.control.NonFatal
-var source: Option[Source] = None
+var string: Option[Int] = None
try
- source = Some(Source.fromFile("README.md"))
- // ...
+ string = Some(Integer.parseInt("foo"))
+ println(s"Parsed \"foo\" to ${string}")
catch
- case NonFatal(ex) => println(ex)
+ case NonFatal(ex) => println(s"NonFatal thrown: ${ex}")
finally
- if source != None then
- source.get.close
+ if string == None then
+ println("string is None")
end if
end try
@@ -112,11 +112,11 @@ val longMon =
end new // You can use "end new" here because "new Monoid..." starts at the same column!
// New type class given instantiation
-given floatMonoid: Monoid[Float] with
+given floatMonoid: Monoid[Float]:
def add(f1: Float, f2: Float): Float = f1+f2
def zero: Float = 0.0F
end floatMonoid // Use identifier.
-given Monoid[Double] with
+given Monoid[Double]:
def add(d1: Double, d2: Double): Double = d1+d2
def zero: Double = 0.0
end given // Anonymous, so no identifier. Hence, use "given".
diff --git a/src/script/scala/progscala3/appdesign/Deprecated.scala b/src/script/scala/progscala3/appdesign/Deprecated.scala
index bafd07af..1b0529a1 100644
--- a/src/script/scala/progscala3/appdesign/Deprecated.scala
+++ b/src/script/scala/progscala3/appdesign/Deprecated.scala
@@ -6,7 +6,7 @@ import scala.annotation.nowarn // This one has to be imported.
@deprecated("this method will be removed", "V1.2.3")
def obsolete(i: Int) = 2*i
-def warning(i: Int) = obsolete(i)
+def warning(i: Int) = obsolete(i) // ERROR
// In Scala 2, @nowarn would suppress a warning for this method's use of obsolete.
// This is not (yet?) implemented in Scala 3.
@nowarn def nowarning(i: Int) = obsolete(i)
diff --git a/src/script/scala/progscala3/basicoop/DollarsPercentagesOpaque.scala b/src/script/scala/progscala3/basicoop/DollarsPercentagesOpaque.scala
index 1cf0f2ef..bda44d37 100644
--- a/src/script/scala/progscala3/basicoop/DollarsPercentagesOpaque.scala
+++ b/src/script/scala/progscala3/basicoop/DollarsPercentagesOpaque.scala
@@ -37,5 +37,5 @@ val gross = Dollars(10000.0)
val taxes = Percentage(0.1)
val salary1 = Salary(gross, taxes)
val net1 = salary1.net
-val salary2 = Salary(taxes, gross) // Won't compile!
+val salary2 = Salary(taxes, gross) // ERROR Won't compile!
// end::usage[]
diff --git a/src/script/scala/progscala3/basicoop/GoodBad.scala b/src/script/scala/progscala3/basicoop/GoodBad.scala
index 0f3192ce..0bb506ab 100644
--- a/src/script/scala/progscala3/basicoop/GoodBad.scala
+++ b/src/script/scala/progscala3/basicoop/GoodBad.scala
@@ -2,7 +2,7 @@
object OBad:
def m(seq: Seq[Int]): String = seq.mkString("|")
- def m(seq: Seq[String]): String = seq.mkString(",")
+ def m(seq: Seq[String]): String = seq.mkString(",") // ERROR
trait TGood:
def member(suffix: String): String
@@ -10,4 +10,4 @@ trait TGood:
trait TBad:
def member: String
- val member: String
+ val member: String // ERROR
diff --git a/src/script/scala/progscala3/basicoop/MatchableOpaque.scala b/src/script/scala/progscala3/basicoop/MatchableOpaque.scala
index 10cbb818..db0c8e05 100644
--- a/src/script/scala/progscala3/basicoop/MatchableOpaque.scala
+++ b/src/script/scala/progscala3/basicoop/MatchableOpaque.scala
@@ -5,7 +5,7 @@ object Obj:
opaque type OArr[T] = Array[T]
summon[Obj.Arr[Int] <:< Matchable] // Okay
-summon[Obj.OArr[Int] <:< Matchable] // Doesn't work
+summon[Obj.OArr[Int] <:< Matchable] // ERROR!
object Obj2:
type Arr[T] = Array[T]
diff --git a/src/script/scala/progscala3/basicoop/tagging/Tags.scala b/src/script/scala/progscala3/basicoop/tagging/Tags.scala
index 36b56e99..24ba62ea 100644
--- a/src/script/scala/progscala3/basicoop/tagging/Tags.scala
+++ b/src/script/scala/progscala3/basicoop/tagging/Tags.scala
@@ -13,6 +13,6 @@ val xs: Array[Double @@ Meter] = Array(1.0, 2.0, 3.0).tags
val o: Ordering[Double] = implicitly
val om: Ordering[Double @@ Meter] = o.tags
om.compare(x, x)
-om.compare(x, y) // Compilation Error!
-xs.min(om)
-xs.min(o) // Compilation Error!
+om.compare(x, y) // Compilation ERROR!
+xs.min(using om)
+xs.min(using o) // Compilation ERROR!
diff --git a/src/script/scala/progscala3/basicoop/tagging/Tags2.scala b/src/script/scala/progscala3/basicoop/tagging/Tags2.scala
index 3c12b83d..52138b1a 100644
--- a/src/script/scala/progscala3/basicoop/tagging/Tags2.scala
+++ b/src/script/scala/progscala3/basicoop/tagging/Tags2.scala
@@ -13,6 +13,6 @@ val xs = Array(1.0, 2.0, 3.0).tags[Meter]
val o: Ordering[Double] = implicitly
val om: Ordering[Double @@ Meter] = o.tags
om.compare(x, x)
-om.compare(x, y) // Compilation Error!
-xs.min(om)
-xs.min(o) // Compilation Error!
+om.compare(x, y) // Compilation ERROR!
+xs.min(using om)
+xs.min(using o) // Compilation ERROR!
diff --git a/src/script/scala/progscala3/collections/MultiMap.scala b/src/script/scala/progscala3/collections/MultiMap.scala
index 164052bb..24023876 100644
--- a/src/script/scala/progscala3/collections/MultiMap.scala
+++ b/src/script/scala/progscala3/collections/MultiMap.scala
@@ -1,4 +1,5 @@
// src/script/scala/progscala3/collections/MultiMap.scala
+// NOTE: This file uses deprecated features, like MultiMap.
import collection.mutable.{HashMap, MultiMap, Set} // <1>
val mm = new HashMap[Int, Set[String]] with MultiMap[Int, String] // <2>
diff --git a/src/script/scala/progscala3/contexts/ExtensionMethodScoping.scala b/src/script/scala/progscala3/contexts/ExtensionMethodScoping.scala
index 86ae5ce1..0d46248e 100644
--- a/src/script/scala/progscala3/contexts/ExtensionMethodScoping.scala
+++ b/src/script/scala/progscala3/contexts/ExtensionMethodScoping.scala
@@ -5,7 +5,7 @@ val s = "Hello World!"
trait T:
extension (s: String) def LOUD: String = s.toUpperCase
-s.LOUD // error; LOUD not in scope.
+s.LOUD // ERROR; LOUD not in scope.
object S2 extends T:
def loud(s: String): String = s.LOUD
@@ -15,7 +15,7 @@ S2.loud(s)
object S2:
extension (s: String) def soft: String = s.toLowerCase
-s.soft
+s.soft // ERROR; soft not in scope.
import S2.soft
s.soft
diff --git a/src/script/scala/progscala3/contexts/GivenImports.scala b/src/script/scala/progscala3/contexts/GivenImports.scala
index e2e2a51e..f76a117a 100644
--- a/src/script/scala/progscala3/contexts/GivenImports.scala
+++ b/src/script/scala/progscala3/contexts/GivenImports.scala
@@ -23,7 +23,7 @@ trait Marker[T]
object O2:
class C1
given C1 = C1()
- // In Scala 3.0.0, the following has to be written: given Marker[Int] with {}
+ // In Scala 3.0.0, the following has to be written: given Marker[Int]: {}
given Marker[Int]() // <1>
given Marker[List[?]]() // <2>
diff --git a/src/script/scala/progscala3/contexts/ImplicitNotFound.scala b/src/script/scala/progscala3/contexts/ImplicitNotFound.scala
index 73ba013e..cc36e6ef 100644
--- a/src/script/scala/progscala3/contexts/ImplicitNotFound.scala
+++ b/src/script/scala/progscala3/contexts/ImplicitNotFound.scala
@@ -19,9 +19,9 @@ object O:
// end::definitions[]
// tag::usage[]
-given Tagify[Int] with
+given Tagify[Int]:
def toTag(i: Int): String = s"$i"
-given Tagify[String] with
+given Tagify[String]:
def toTag(s: String): String = s"$s"
Stringer("Hello World!")
diff --git a/src/script/scala/progscala3/contexts/MatchGivens.scala b/src/script/scala/progscala3/contexts/MatchGivens.scala
index 6c516927..1619d037 100644
--- a/src/script/scala/progscala3/contexts/MatchGivens.scala
+++ b/src/script/scala/progscala3/contexts/MatchGivens.scala
@@ -9,16 +9,16 @@ def useWitness(using Witness): String = summon[Witness].toString // <2>
// end::definitions[]
// tag::usage[]
-useWitness // <1>
+useWitness // ERROR // <1>
for given Witness <- Seq(IntWitness, StringWitness) // <2>
do println(useWitness)
-useWitness // <3>
+useWitness // ERROR // <3>
Seq(IntWitness -> "Int", StringWitness -> "String") foreach { // <4>
case (witness @ given Witness, y) => println(s"witness: $useWitness -> $y")
}
-useWitness // <5>
+useWitness // ERROR // <5>
// end::usage[]
diff --git a/src/script/scala/progscala3/contexts/SeqUnzip.scala b/src/script/scala/progscala3/contexts/SeqUnzip.scala
index f6d31e9f..1a1eda4c 100644
--- a/src/script/scala/progscala3/contexts/SeqUnzip.scala
+++ b/src/script/scala/progscala3/contexts/SeqUnzip.scala
@@ -4,7 +4,7 @@
val seq = (0 to 10).toList
object noimplicit:
- val unzipped = seq.unzip // Error.
+ val unzipped = seq.unzip // ERROR
object topair:
implicit val toPair: Int => (Int, String) = i => (i, (2*i).toString)
diff --git a/src/script/scala/progscala3/contexts/UsingClauses.scala b/src/script/scala/progscala3/contexts/UsingClauses.scala
index fb4d384e..d0ceb10d 100644
--- a/src/script/scala/progscala3/contexts/UsingClauses.scala
+++ b/src/script/scala/progscala3/contexts/UsingClauses.scala
@@ -3,13 +3,13 @@
case class SortableSeq[A](seq: Seq[A]):
def sortBy1a[B](transform: A => B)(using o: Ordering[B]): SortableSeq[A] =
- SortableSeq(seq.sortBy(transform)(o))
+ SortableSeq(seq.sortBy(transform)(using o))
def sortBy1b[B](transform: A => B)(using Ordering[B]): SortableSeq[A] =
- SortableSeq(seq.sortBy(transform)(summon[Ordering[B]]))
+ SortableSeq(seq.sortBy(transform)(using summon[Ordering[B]]))
def sortBy2[B : Ordering](transform: A => B): SortableSeq[A] =
- SortableSeq(seq.sortBy(transform)(summon[Ordering[B]]))
+ SortableSeq(seq.sortBy(transform)(using summon[Ordering[B]]))
// end::definitions[]
// tag::defaultOrdering[]
@@ -25,7 +25,9 @@ defaultOrdering()
// tag::oddEvenImplicitOrdering[]
def oddEvenImplicitOrdering() =
- implicit val oddEven: Ordering[Int] = new Ordering[Int]:
+ // DeanW - Sept 2025: implicit vals are no longer supported:
+ // implicit val oddEven: Ordering[Int] = new Ordering[Int]:
+ given oddEven: Ordering[Int] = new Ordering[Int]:
def compare(i: Int, j: Int): Int = i%2 compare j%2 match
case 0 => i compare j
case c => c
@@ -45,7 +47,7 @@ oddEvenImplicitOrdering()
// tag::oddEvenGivenOrdering[]
def evenOddGivenOrdering() =
- given evenOdd: Ordering[Int] with
+ given evenOdd: Ordering[Int]:
def compare(i: Int, j: Int): Int = i%2 compare j%2 match
case 0 => i compare j
case c => -c
diff --git a/src/script/scala/progscala3/contexts/UsingTypeErasureWorkaround.scala b/src/script/scala/progscala3/contexts/UsingTypeErasureWorkaround.scala
index 7be90e44..30ab1d9f 100644
--- a/src/script/scala/progscala3/contexts/UsingTypeErasureWorkaround.scala
+++ b/src/script/scala/progscala3/contexts/UsingTypeErasureWorkaround.scala
@@ -4,7 +4,7 @@
object O2:
trait Marker[T] // <1>
// In Scala 3.0.0, the following has to be written:
- // given IntMarker: Marker[Int] with {}
+ // given IntMarker: Marker[Int]: {}
given IntMarker: Marker[Int]()
given StringMarker: Marker[String]()
diff --git a/src/script/scala/progscala3/contexts/typeclass/MonoidAliasGiven.scala b/src/script/scala/progscala3/contexts/typeclass/MonoidAliasGiven.scala
index d7c78bc3..cbe1c1f3 100644
--- a/src/script/scala/progscala3/contexts/typeclass/MonoidAliasGiven.scala
+++ b/src/script/scala/progscala3/contexts/typeclass/MonoidAliasGiven.scala
@@ -2,7 +2,7 @@
// src/script/scala/progscala3/contexts/typeclass/MonoidAliasGiven.scala
import progscala3.contexts.typeclass.Monoid
-given NumericMonoid2[T : Numeric]: Monoid[T] = new Monoid[T]:
+given NumericMonoid2: [T: Numeric] => Monoid[T] = new Monoid[T]:
println("Initializing NumericMonoid2")
def unit: T = summon[Numeric[T]].zero
extension (t: T) infix def combine(other: T): T =
diff --git a/src/script/scala/progscala3/contexts/typeclass/MonoidTypeClass.scala b/src/script/scala/progscala3/contexts/typeclass/MonoidTypeClass.scala
index fe051cb8..864e49af 100644
--- a/src/script/scala/progscala3/contexts/typeclass/MonoidTypeClass.scala
+++ b/src/script/scala/progscala3/contexts/typeclass/MonoidTypeClass.scala
@@ -16,7 +16,10 @@ IntMonoid.unit <+> 2 // 2
// end::usage[]
// tag::numericdefinition[]
-given NumericMonoid[T : Numeric]: Monoid[T] with
+// 2025-06-16: The following line was the original syntax in Scala 3
+// given NumericMonoid[T : Numeric]: Monoid[T]:
+// This is the currently acceptable syntax:
+given NumericMonoid: [T: Numeric] => Monoid[T]:
def unit: T = summon[Numeric[T]].zero
extension (t: T)
infix def combine(other: T): T = summon[Numeric[T]].plus(t, other)
@@ -30,25 +33,6 @@ NumericMonoid[BigDecimal].unit <+> BigDecimal(3.14)
NumericMonoid[BigDecimal].unit combine BigDecimal(3.14)
// end::numericdefinition[]
-// tag::numericdefinition2[]
-given NumericMonoid[T](using num: Numeric[T]): Monoid[T] with
- def unit: T = num.zero
- extension (t: T)
- infix def combine(other: T): T = num.plus(t, other)
-// end::numericdefinition2[]
-
-// tag::numericdefinition3[]
-given [T : Numeric]: Monoid[T] with
- def unit: T = summon[Numeric[T]].zero
- extension (t: T)
- infix def combine(other: T): T = summon[Numeric[T]].plus(t, other)
-// or
-given [T](using num: Numeric[T]): Monoid[T] with
- def unit: T = summon[Numeric[T]].zero
- extension (t: T)
- infix def combine(other: T): T = summon[Numeric[T]].plus(t, other)
-
-BigDecimal(3.14) <+> summon[Monoid[BigDecimal]].unit
-summon[Monoid[BigDecimal]].unit <+> BigDecimal(3.14)
-summon[Monoid[BigDecimal]].unit combine BigDecimal(3.14)
-// end::numericdefinition3[]
+// See MonoidTypeClass2.scala for an alternative definition.
+// We can't write it in this file because loading the script causes errors
+// with alternative definitions in scope.
diff --git a/src/script/scala/progscala3/contexts/typeclass/MonoidTypeClass2.scala b/src/script/scala/progscala3/contexts/typeclass/MonoidTypeClass2.scala
new file mode 100644
index 00000000..455a533a
--- /dev/null
+++ b/src/script/scala/progscala3/contexts/typeclass/MonoidTypeClass2.scala
@@ -0,0 +1,25 @@
+// tag::usage[]
+// src/script/scala/progscala3/contexts/typeclass/MonoidTypeClass2.scala
+import progscala3.contexts.typeclass.{Monoid, given} // <1>
+
+// 2025-06-16: This file was split off from MonoidTypeClass.scala to
+// to eliminate errors that occur with the effectively duplicate definitions
+// of monoids for Numerics. Here, we use an anonymous given, rather than the
+// named given in the other file. Note the use of summon below.
+
+// tag::numericdefinition3[]
+given [T : Numeric] => Monoid[T]:
+ def unit: T = summon[Numeric[T]].zero
+ extension (t: T)
+ infix def combine(other: T): T = summon[Numeric[T]].plus(t, other)
+// or
+// 2025-06-16: This alternative syntax no longer works. A replacement is TBD.
+// given [T](using num: Numeric[T]): Monoid[T]:
+// def unit: T = summon[Numeric[T]].zero
+// extension (t: T)
+// infix def combine(other: T): T = summon[Numeric[T]].plus(t, other)
+
+BigDecimal(3.14) <+> summon[Monoid[BigDecimal]].unit
+summon[Monoid[BigDecimal]].unit <+> BigDecimal(3.14)
+summon[Monoid[BigDecimal]].unit combine BigDecimal(3.14)
+// end::numericdefinition3[]
diff --git a/src/script/scala/progscala3/contexts/typeclass/TypeClassSubtypingProblems.scala b/src/script/scala/progscala3/contexts/typeclass/TypeClassSubtypingProblems.scala
index dfa15996..f74c74b9 100644
--- a/src/script/scala/progscala3/contexts/typeclass/TypeClassSubtypingProblems.scala
+++ b/src/script/scala/progscala3/contexts/typeclass/TypeClassSubtypingProblems.scala
@@ -15,7 +15,7 @@ import progscala3.contexts.{DomainConcept, Address, Person}
// Helper methods are used, like in
// src/main/scala/progscala3/contexts/typeclass/new3/ToJSONTypeClasses.scala
// for reasons explained below.
-given ToJSON[Address] with
+given ToJSON[Address]:
def toJSON2(address: Address, name: String, level: Int): String =
val (outdent, indent) = indentation(level)
s""""$name": {
@@ -26,7 +26,7 @@ given ToJSON[Address] with
def toJSON(name: String = "", level: Int = 0): String =
toJSON2(address, name, level)
-given ToJSON[Person] with
+given ToJSON[Person]:
def toJSON2(person: Person, name: String, level: Int): String =
val (outdent, indent) = indentation(level)
s""""$name": {
@@ -64,7 +64,7 @@ list1.map(_.toJSON("object"))
// switches on the actual type. This is ugly and you'll have to remember to update
// this method if you change the subtypes of DomainConcept. Note that I declared
// it to be a sealed trait above, which lets the compiler catch some problems.
-given ToJSON[DomainConcept] with
+given ToJSON[DomainConcept]:
extension (dc: DomainConcept)
def toJSON(name: String = "", level: Int = 0): String = dc match
case person: Person => summon[ToJSON[Person]].toJSON2(person, name, level)
@@ -82,12 +82,13 @@ list1.map(_.toJSON("object"))
// Well, an infinite recursion occurs!! This is because, for example,
// person.toJSON() will now recursively call the DomainConcept.toJSON
// extension method, instead of the original Person.toJSON.
+// Try it yourself; uncomment the following given definition and comment out
+// the previous definition of ToJSON[DomainConcept], then rerun this script.
+// given ToJSON[DomainConcept]:
+// extension (dc: DomainConcept)
+// def toJSON(name: String = "", level: Int = 0): String = dc match
+// case person: Person => person.toJSON(name, level)
+// case address: Address => address.toJSON(name, level)
-given ToJSON[DomainConcept] with
- extension (dc: DomainConcept)
- def toJSON(name: String = "", level: Int = 0): String = dc match
- case person: Person => person.toJSON(name, level)
- case address: Address => address.toJSON(name, level)
-
-list1.map(_.toJSON()) // BOOM!!
+// list1.map(_.toJSON("object")) // BOOM - Stack overflow!!
// END 4
diff --git a/src/script/scala/progscala3/meta/compiletime/SummonAll.scala b/src/script/scala/progscala3/meta/compiletime/SummonAll.scala
index 6e908e64..7bc00a02 100644
--- a/src/script/scala/progscala3/meta/compiletime/SummonAll.scala
+++ b/src/script/scala/progscala3/meta/compiletime/SummonAll.scala
@@ -2,9 +2,9 @@
import scala.compiletime.summonAll
trait C; trait D; trait E
-// In Scala 3.0.0, the following has to be written: given c: C with {}
+// In Scala 3.0.0, the following has to be written: given c: C: {}
given c: C()
given d: D()
summonAll[C *: D *: EmptyTuple]
-summonAll[C *: D *: E *: EmptyTuple] // <1>
+summonAll[C *: D *: E *: EmptyTuple] // ERROR // <1>
diff --git a/src/script/scala/progscala3/meta/compiletime/SummonFrom.scala b/src/script/scala/progscala3/meta/compiletime/SummonFrom.scala
index ab675337..cc25dbcf 100644
--- a/src/script/scala/progscala3/meta/compiletime/SummonFrom.scala
+++ b/src/script/scala/progscala3/meta/compiletime/SummonFrom.scala
@@ -14,7 +14,7 @@ inline def trySummonFrom(label: String, expected: Int): Unit = // <1>
def tryNone = trySummonFrom("tryNone:", 0) // <2>
def tryA = // <3>
- // In Scala 3.0.0, the following has to be written: given A with {}
+ // In Scala 3.0.0, the following has to be written: given A: {}
given A()
trySummonFrom("tryA:", 1)
diff --git a/src/script/scala/progscala3/meta/inline/ConditionalMatch.scala b/src/script/scala/progscala3/meta/inline/ConditionalMatch.scala
index 1d15db8c..1823bb49 100644
--- a/src/script/scala/progscala3/meta/inline/ConditionalMatch.scala
+++ b/src/script/scala/progscala3/meta/inline/ConditionalMatch.scala
@@ -5,8 +5,10 @@ inline def repeat2(s: String, count: Int): String =
else s + repeat2(s, count-1)
repeat2("hello", 3) // Okay
-val n = 3
-repeat2("hello", n) // ERROR! (try "inline val n = 3")
+inline val n1 = 3
+repeat2("hello", n1) // Okay, because m is declared inline
+val n2 = 3
+repeat2("hello", n2) // ERROR!
inline def repeat3(s: String, count: Int): String =
inline count match
@@ -14,5 +16,7 @@ inline def repeat3(s: String, count: Int): String =
case _ => s + repeat3(s, count-1)
repeat3("hello", 3) // Okay
-var n2 = 3
-repeat3("hello", n2) // ERROR!
+inline var n3 = 3 // ERROR!
+//repeat3("hello", n3)
+var n4 = 3
+repeat3("hello", n4) // ERROR!
diff --git a/src/script/scala/progscala3/meta/inline/Recursive.scala b/src/script/scala/progscala3/meta/inline/Recursive.scala
index acf3e063..c47d1e19 100644
--- a/src/script/scala/progscala3/meta/inline/Recursive.scala
+++ b/src/script/scala/progscala3/meta/inline/Recursive.scala
@@ -5,5 +5,7 @@ inline def repeat(s: String, count: Int): String =
else s + repeat(s, count-1)
repeat("hello", 3) // Okay
-val n=3
-repeat("hello", n) // ERROR! (try "inline val n = 3")
+inline val m = 3
+repeat("hello", m) // Okay, because m is declared inline
+val n = 3
+repeat("hello", n) // ERROR!
diff --git a/src/script/scala/progscala3/objectsystem/variance/MutableVariance.scala b/src/script/scala/progscala3/objectsystem/variance/MutableVariance.scala
index 59e73964..868c6054 100644
--- a/src/script/scala/progscala3/objectsystem/variance/MutableVariance.scala
+++ b/src/script/scala/progscala3/objectsystem/variance/MutableVariance.scala
@@ -7,19 +7,19 @@ class Contravariant[-A](var mut: A) // ERROR
// end::first[]
// tag::second[]
-class Invariant[A](val mutInit: A): // Okay
+class Invariant2[A](val mutInit: A): // Okay
private var _mut: A = mutInit
def mut_=(a: A): Unit = _mut = a
def mut: A = _mut
// end::second[]
// tag::third[]
-class Covariant[+A](val mutInit: A): // ERROR
+class Covariant2[+A](val mutInit: A): // ERROR
private var _mut: A = mutInit
def mut_=(a: A): Unit = _mut = a
def mut: A = _mut
-class Contravariant[-A](val mutInit: A): // ERROR
+class Contravariant2[-A](val mutInit: A): // ERROR
private var _mut: A = mutInit
def mut_=(a: A): Unit = _mut = a
def mut: A = _mut
diff --git a/src/script/scala/progscala3/patternmatching/MatchExhaustive.scala b/src/script/scala/progscala3/patternmatching/MatchExhaustive.scala
index 4f39af45..f3b7822a 100644
--- a/src/script/scala/progscala3/patternmatching/MatchExhaustive.scala
+++ b/src/script/scala/progscala3/patternmatching/MatchExhaustive.scala
@@ -2,5 +2,5 @@
val seq3 = Seq(Some(1), None, Some(2), None)
val result3 = seq3.map {
- case Some(i) => s"int $i"
+ case Some(i) => s"int $i" // ERROR
}
diff --git a/src/script/scala/progscala3/patternmatching/MatchForFiltering.scala b/src/script/scala/progscala3/patternmatching/MatchForFiltering.scala
index 0a143f75..b8891744 100644
--- a/src/script/scala/progscala3/patternmatching/MatchForFiltering.scala
+++ b/src/script/scala/progscala3/patternmatching/MatchForFiltering.scala
@@ -5,7 +5,7 @@ val elems = Seq((1, 2), "hello", (3, 4), 1, 2.2, (5, 6))
val what1 = for (case (x, y) <- elems) yield (y, x) // <1>
val what2 = for case (x, y) <- elems yield (y, x)
-val nope = for (x, y) <- elems yield (y, x)
+val nope = for (x, y) <- elems yield (y, x) // ERROR
val seq = Seq(None, Some(1), None, Some(2.2), None, None, Some("three"))
for case Some(x) <- seq yield x
diff --git a/src/script/scala/progscala3/patternmatching/MatchRepeatedParamsList.scala b/src/script/scala/progscala3/patternmatching/MatchRepeatedParamsList.scala
index 8f671d45..80220fa4 100644
--- a/src/script/scala/progscala3/patternmatching/MatchRepeatedParamsList.scala
+++ b/src/script/scala/progscala3/patternmatching/MatchRepeatedParamsList.scala
@@ -32,7 +32,6 @@ val results = wheres.map {
val valStr = (val1 +: vals).mkString(", ")
s"WHERE $col IN ($valStr)"
case WhereOp(col, op, value) => s"WHERE $col ${op.symbol} $value"
- case x => s"ERROR: Unknown expression: $x"
}
assert(results == Seq(
"WHERE state IN (IL, CA, VA)",
diff --git a/src/script/scala/progscala3/patternmatching/MatchSurprise.scala b/src/script/scala/progscala3/patternmatching/MatchSurprise.scala
index 22306fd9..81291fc4 100644
--- a/src/script/scala/progscala3/patternmatching/MatchSurprise.scala
+++ b/src/script/scala/progscala3/patternmatching/MatchSurprise.scala
@@ -5,7 +5,7 @@ def checkYBad(y: Int): Seq[String] =
for x <- Seq(99, 100, 101)
yield x match
case y => "found y!"
- case i: Int => "int: "+i // Unreachable case!
+ case i: Int => "int: "+i // ERROR Unreachable case!
// end::bad[]
// tag::good1[]
diff --git a/src/script/scala/progscala3/patternmatching/MatchTreeADTFull.scala b/src/script/scala/progscala3/patternmatching/MatchTreeADTFull.scala
index 720220f1..add25177 100644
--- a/src/script/scala/progscala3/patternmatching/MatchTreeADTFull.scala
+++ b/src/script/scala/progscala3/patternmatching/MatchTreeADTFull.scala
@@ -31,3 +31,4 @@ yield tree match
r @ Branch(rl @ Leaf(rli), rr @ Branch(_,_))) =>
s"3: l=$l, r=$r, rl=$rl, rli=$rli, rr=$rr"
case _:Branch[?] => "4: Other Branch"
+ case Leaf(_) => "5: Leaf"
diff --git a/src/script/scala/progscala3/patternmatching/Matchable.scala b/src/script/scala/progscala3/patternmatching/Matchable.scala
index d3e97003..07240c9d 100644
--- a/src/script/scala/progscala3/patternmatching/Matchable.scala
+++ b/src/script/scala/progscala3/patternmatching/Matchable.scala
@@ -3,13 +3,13 @@
val iarray = IArray(1,2,3,4,5)
iarray match
- case a: Array[Int] => a(2) = 300 // Scala 3 warning!!
+ case a: Array[Int] => a(2) = 300 // ERROR: Scala 3 warning!!
println(iarray)
// end::iarray[]
// tag::unbounded[]
def examine[T](seq: Seq[T]): Seq[String] = seq.map {
- case i: Int => s"Int: $i"
+ case i: Int => s"Int: $i" // ERROR: Scala 3 warning!!
case other => s"Other: $other"
}
// end::unbounded[]
diff --git a/src/script/scala/progscala3/patternmatching/UnapplySingleValue2.scala b/src/script/scala/progscala3/patternmatching/UnapplySingleValue2.scala
index 83b9b54c..553f061c 100644
--- a/src/script/scala/progscala3/patternmatching/UnapplySingleValue2.scala
+++ b/src/script/scala/progscala3/patternmatching/UnapplySingleValue2.scala
@@ -21,11 +21,11 @@ val set = map.keySet
// Works:
map match
- case JHashMapWrapper(jmap) => jmap
+ case JHashMapWrapper(jmap) => map
set match
- case JHashSetWrapper(jset) => jset
+ case JHashSetWrapper(jset) => set
// This fails to compile because there are _two_ possible returned values.
for x <- Seq(map, set) yield x match
- case JHashMapWrapper(jmap) => jmap
- case JHashSetWrapper(jset) => jset
+ case JHashMapWrapper(jmap) => map // ERROR
+ case JHashSetWrapper(jset) => set // ERROR
diff --git a/src/script/scala/progscala3/rounding/InfixMethod.scala b/src/script/scala/progscala3/rounding/InfixMethod.scala
index 6254188f..796a609f 100644
--- a/src/script/scala/progscala3/rounding/InfixMethod.scala
+++ b/src/script/scala/progscala3/rounding/InfixMethod.scala
@@ -4,9 +4,9 @@ case class Foo(str: String):
def append(s: String): Foo = copy(str + s)
infix def combine(s:String): Foo = append(s)
-Foo("one").append("two") // <1>
-Foo("one") append {"two"} // <2>
+Foo("one").append("two") // <1>
+Foo("one") append {"two"} // <2>
Foo("one") `append` "two"
-Foo("one") append "two" // <3>
+Foo("one") append "two" // ERROR // <3>
-Foo("one") combine "two" // <4>
+Foo("one") combine "two" // <4>
diff --git a/src/script/scala/progscala3/rounding/InfixType.scala b/src/script/scala/progscala3/rounding/InfixType.scala
index a0f5152a..ed22b3be 100644
--- a/src/script/scala/progscala3/rounding/InfixType.scala
+++ b/src/script/scala/progscala3/rounding/InfixType.scala
@@ -2,9 +2,9 @@
import scala.annotation.targetName
@targetName("TIEFighter") case class <+>[A,B](a: A, b: B) // <1>
-val ab1: Int <+> String = 1 <+> "one" // <2>
+val ab1: Int <+> String = 1 <+> "one" // ERROR // <2>
val ab2: Int <+> String = <+>(1, "one") // <3>
infix case class tie[A,B](a: A, b: B) // <4>
-val ab3: Int tie String = 1 tie "one"
+val ab3: Int tie String = 1 tie "one" // ERROR
val ab4: Int tie String = tie(1, "one")
diff --git a/src/script/scala/progscala3/rounding/TypeErasureProblem.scala b/src/script/scala/progscala3/rounding/TypeErasureProblem.scala
index 3c352c4a..a79633d2 100644
--- a/src/script/scala/progscala3/rounding/TypeErasureProblem.scala
+++ b/src/script/scala/progscala3/rounding/TypeErasureProblem.scala
@@ -2,4 +2,4 @@
object O:
def m(is: Seq[Int]): Int = is.sum
- def m(ss: Seq[String]): Int = ss.length
+ def m(ss: Seq[String]): Int = ss.length // ERROR
diff --git a/src/script/scala/progscala3/typelessdomore/FibonacciTailrec.scala b/src/script/scala/progscala3/typelessdomore/FibonacciTailrec.scala
index 361beba5..a7d29928 100644
--- a/src/script/scala/progscala3/typelessdomore/FibonacciTailrec.scala
+++ b/src/script/scala/progscala3/typelessdomore/FibonacciTailrec.scala
@@ -4,6 +4,6 @@ import scala.annotation.tailrec
@tailrec
def fibonacci(i: Int): BigInt =
if i <= 1 then BigInt(1)
- else fibonacci(i - 2) + fibonacci(i - 1) // Error with @tailrec
+ else fibonacci(i - 2) + fibonacci(i - 1) // ERROR with @tailrec
(0 to 5).foreach(i => println(s"$i: ${fibonacci(i)}"))
diff --git a/src/script/scala/progscala3/typelessdomore/RepeatedParameters.scala b/src/script/scala/progscala3/typelessdomore/RepeatedParameters.scala
index 8e253268..fe9c244b 100644
--- a/src/script/scala/progscala3/typelessdomore/RepeatedParameters.scala
+++ b/src/script/scala/progscala3/typelessdomore/RepeatedParameters.scala
@@ -12,7 +12,7 @@ object Mean1:
// tag::mean2[]
object Mean2:
def apply(ds: Double*): Double = apply(ds)
- def apply(ds: Seq[Double]): Double = ds.sum/ds.size
+ def apply(ds: Seq[Double]): Double = ds.sum/ds.size // ERROR
// end::mean2[]
// tag::mean[]
diff --git a/src/script/scala/progscala3/typesystem/bounds/ViewToContextBounds.scala b/src/script/scala/progscala3/typesystem/bounds/ViewToContextBounds.scala
index 088e2ce1..742fbed9 100644
--- a/src/script/scala/progscala3/typesystem/bounds/ViewToContextBounds.scala
+++ b/src/script/scala/progscala3/typesystem/bounds/ViewToContextBounds.scala
@@ -15,8 +15,7 @@ import Serialization.*
object RemoteConnection:
def write[T : Writable](t: T): String =
- val writable = implicitly
- writable(t).serialized // Return a string "as the remote connection"
+ summon[Writable[T]](t).serialized // Return a string "as the remote connection"
assert(RemoteConnection.write(100) == "-- 100 --")
assert(RemoteConnection.write(3.14f) == "-- 3.14 --")
diff --git a/src/script/scala/progscala3/typesystem/higherkinded/HKFoldLeft.scala b/src/script/scala/progscala3/typesystem/higherkinded/HKFoldLeft.scala
index cd96b72e..d24d0baf 100644
--- a/src/script/scala/progscala3/typesystem/higherkinded/HKFoldLeft.scala
+++ b/src/script/scala/progscala3/typesystem/higherkinded/HKFoldLeft.scala
@@ -6,14 +6,14 @@ object HKFoldLeft: // "HK" for "higher-kinded"
trait Folder[-M[_]]: // <1>
def apply[IN, OUT](m: M[IN], seed: OUT, f: (OUT, IN) => OUT): OUT
- given Folder[Iterable] with // <2>
+ given Folder[Iterable]: // <2>
def apply[IN, OUT](iter: Iterable[IN],
seed: OUT, f: (OUT, IN) => OUT): OUT =
var accumulator = seed
iter.foreach(t => accumulator = f(accumulator, t))
accumulator
- given Folder[Option] with // <3>
+ given Folder[Option]: // <3>
def apply[IN, OUT](opt: Option[IN],
seed: OUT, f: (OUT, IN) => OUT): OUT = opt match
case Some(t) => f(seed, t)
@@ -44,7 +44,7 @@ HKFoldLeft(Option.empty[Int])(0.0)(_+_)
// end::usage1[]
// tag::usage2[]
-given Folder[[X] =>> Either[String, X]] with
+given Folder[[X] =>> Either[String, X]]:
def apply[IN, OUT](err: Either[String, IN],
seed: OUT, f: (OUT, IN) => OUT): OUT = err match
case Right(t) => f(seed, t)
diff --git a/src/script/scala/progscala3/typesystem/intersectionunion/Union.scala b/src/script/scala/progscala3/typesystem/intersectionunion/Union.scala
index b4492035..0e3cf7a2 100644
--- a/src/script/scala/progscala3/typesystem/intersectionunion/Union.scala
+++ b/src/script/scala/progscala3/typesystem/intersectionunion/Union.scala
@@ -83,7 +83,7 @@ val fABC1: (A | B | C) => String = _ match
case t1: A => "A"
case t2: B => "B"
case t3: C => "C"
-val fABC2: (A => String) & (B => String) & (C => String) = fABC
+val fABC2: (A => String) & (B => String) & (C => String) = fABC1
val seqABCs: Seq[A | B | C] = Seq(a, b, c)
seqABCs.map(fABC1)
diff --git a/src/script/scala/progscala3/typesystem/matchtypes/MatchTypes2.scala b/src/script/scala/progscala3/typesystem/matchtypes/MatchTypes2.scala
index 49c5d825..2b83eb67 100644
--- a/src/script/scala/progscala3/typesystem/matchtypes/MatchTypes2.scala
+++ b/src/script/scala/progscala3/typesystem/matchtypes/MatchTypes2.scala
@@ -29,16 +29,16 @@ lastUnsafe(Seq(1,2,3))
lastUnsafe(Some(2))
lastUnsafe(10)
// But...
-lastUnsafe("")
-lastUnsafe(Array.empty[Int])
-lastUnsafe(Nil)
-lastUnsafe(None)
+lastUnsafe("") // ERROR
+lastUnsafe(Array.empty[Int]) // ERROR
+lastUnsafe(Nil) // ERROR
+lastUnsafe(None) // ERROR
// Does this parse and provide a safe implementation? No; it has trouble
// confirming that Char =:= Elem[String] for "s.last" and
// Matchable =:= Elem[Matchable] for the default Matchable clause.
-def lastOrElse[X <: Matchable](in: X)(default: => Elem[X]): Elem[X] = in.asMatchable match
+def lastOrElse[X <: Matchable](in: X)(default: => Elem[X]): Elem[X] = in.asMatchable match // ERROR
case s: String => if s.isEmpty then default else s.last
case a: Array[Elem[X]] => if a.isEmpty then default else a.last
case i: Iterable[Elem[X]] => if i.isEmpty then default else i.last
@@ -46,7 +46,7 @@ def lastOrElse[X <: Matchable](in: X)(default: => Elem[X]): Elem[X] = in.asMatch
case m: Matchable => m
// What about using options? Same parsing problems...
-def lastOption[X <: Matchable](in: X): Option[Elem[X]] = in.asMatchable match
+def lastOption[X <: Matchable](in: X): Option[Elem[X]] = in.asMatchable match // ERROR
case s: String => s.lastOption
case a: Array[Elem[X]] => a.lastOption
case i: Iterable[Elem[X]] => i.lastOption
@@ -56,7 +56,7 @@ def lastOption[X <: Matchable](in: X): Option[Elem[X]] = in.asMatchable match
// Surely this will work, no? No; it seems that any attempt to combine the
// match type Elem[X] with other types, whether "| Null", Option[...], or
// using it as the type of a default argument fails to work.
-def lastOrNull[X <: Matchable](in: X): Elem[X] | Null = in.asMatchable match
+def lastOrNull[X <: Matchable](in: X): Elem[X] | Null = in.asMatchable match // ERROR
case s: String => if s.isEmpty then null else s.last
case a: Array[Elem[X]] => if a.isEmpty then null else a.last
case i: Iterable[Elem[X]] => if i.isEmpty then null else i.last
@@ -72,7 +72,7 @@ type RElem[X <: Matchable] = X match
case Option[t] => RElem[t]
case Matchable => X
-def rlastUnsafe[X <: Matchable](in: X): RElem[X] = in.asMatchable match
+def rlastUnsafe[X <: Matchable](in: X): RElem[X] = in.asMatchable match // ERROR
case s: String => s.last
case a: Array[t] => rlastUnsafe(a.last)
case i: Iterable[t] => rlastUnsafe(i.last)
@@ -86,21 +86,21 @@ def rlastUnsafe[X <: Matchable](in: X): RElem[X] = in.asMatchable match
// case o: Option[RElem[X]] => rlastOrElse(o.getOrElse(default))
// case a: Any => a
-def rlastOrElse[X](in: X, default: RElem[X]): RElem[X] = in.asMatchable match
+def rlastOrElse[X](in: X, default: RElem[X]): RElem[X] = in.asMatchable match // ERROR
case s: String => s.lastOption.getOrElse(default)
case a: Array[RElem[X]] => rlastOrElse(a.lastOption.getOrElse(default))
case i: Iterable[RElem[X]] => rlastOrElse(i.lastOption.getOrElse(default))
case o: Option[RElem[X]] => rlastOrElse(o.getOrElse(default))
case a: Any => a
-def rlastOption[X](in: X): Option[RElem[X]] = in.asMatchable match
+def rlastOption[X](in: X): Option[RElem[X]] = in.asMatchable match // ERROR
case s: String => s.lastOption
case a: Array[RElem[X]] => a.lastOption.flatMap(x => rlastOption(x))
case i: Iterable[RElem[X]] => i.lastOption.flatMap(x => rlastOption(x))
case o: Option[RElem[X]] => o.flatMap(x => rlastOption(x))
case a: Any => Some(a)
-def rlastOrNull[X](in: X): RElem[X] | Null = in.asMatchable match
+def rlastOrNull[X](in: X): RElem[X] | Null = in.asMatchable match // ERROR
case s: String => if s.isEmpty then null else s.last
case a: Array[RElem[X]] => if a.isEmpty then null else rlastOrNull(a.last)
case i: Iterable[RElem[X]] => if i.isEmpty then null else rlastOrNull(i.last)
@@ -111,14 +111,14 @@ val str = "01234"
val arr = Array(2.2, 3.3, 4.4, 5.5)
val seq1 = Seq(0.0F, 1.1F, 2.2F, 3.3F)
val seq2 = Seq(Some(0L), None, Some(3L))
-val opt1 = Some("pi" -> Math.Pi)
+val opt1 = Some("pi" -> Math.PI)
val opt2 = Some(Seq(Array("e" -> Math.E)))
val any = 1.1
-val strL = rlastUnsafe(str)
-val arrL = rlastUnsafe(arr)
-val seq1L = rlastUnsafe(seq1)
-val seq2L = rlastUnsafe(seq2)
-val opt1L = rlastUnsafe(opt1)
-val opt2L = rlastUnsafe(opt2)
-val anyL = rlastUnsafe(any)
+val strL = rlastUnsafe(str) // ERROR
+val arrL = rlastUnsafe(arr) // ERROR
+val seq1L = rlastUnsafe(seq1) // ERROR
+val seq2L = rlastUnsafe(seq2) // ERROR
+val opt1L = rlastUnsafe(opt1) // ERROR
+val opt2L = rlastUnsafe(opt2) // ERROR
+val anyL = rlastUnsafe(any) // ERROR
diff --git a/src/script/scala/progscala3/typesystem/typepaths/TypePath.scala b/src/script/scala/progscala3/typesystem/typepaths/TypePath.scala
index ded80d97..6a52b553 100644
--- a/src/script/scala/progscala3/typesystem/typepaths/TypePath.scala
+++ b/src/script/scala/progscala3/typesystem/typepaths/TypePath.scala
@@ -8,4 +8,4 @@ open class Service: // <1>
val s1 = new Service
val s2 = new Service:
- override val logger: Logger = s1.logger // <2>
+ override val logger: Logger = s1.logger // ERROR // <2>
diff --git a/src/script/scala/progscala3/typesystem/valuetypes/SingletonTypes.scala b/src/script/scala/progscala3/typesystem/valuetypes/SingletonTypes.scala
index df99f8a7..6c036e55 100644
--- a/src/script/scala/progscala3/typesystem/valuetypes/SingletonTypes.scala
+++ b/src/script/scala/progscala3/typesystem/valuetypes/SingletonTypes.scala
@@ -10,4 +10,4 @@ val c1 = C("c1")
println(c1)
val c1b: c1.type = c1 // <2>
println(c1b)
-val c1c: c1.type = C("c1") // <3>
+val c1c: c1.type = C("c1") // ERROR // <3>
diff --git a/src/test/scala/progscala3/contexts/CustomStringInterpolatorSuite.scala b/src/test/scala/progscala3/contexts/CustomStringInterpolatorSuite.scala
index 88e1291c..cd49c5de 100644
--- a/src/test/scala/progscala3/contexts/CustomStringInterpolatorSuite.scala
+++ b/src/test/scala/progscala3/contexts/CustomStringInterpolatorSuite.scala
@@ -6,7 +6,12 @@ import munit.*
class CustomStringInterpolatorSuite extends FunSuite:
object JSONToMapInterpolator:
- implicit class mapForStringContext(val sc: StringContext): // <1>
+ /* DeanW: September 2025. Scala is dropping support for implicit classes,
+ * so we use an extension method instead:
+ * implicit class mapForStringContext(val sc: StringContext):
+ * def map(values: String*): Map[String, String] = ...
+ */
+ extension (sc: StringContext) // <1>
def map(values: String*): Map[String, String] = // <2>
val keyRE = """^[\s{,]*(\S+):\s*""".r // <3>
val keys = sc.parts map { // <4>
diff --git a/src/test/scala/progscala3/contexts/ImplicitConversionResolutionSuite.scala b/src/test/scala/progscala3/contexts/ImplicitConversionResolutionSuite.scala
index 632c5fcc..8d3db3f6 100644
--- a/src/test/scala/progscala3/contexts/ImplicitConversionResolutionSuite.scala
+++ b/src/test/scala/progscala3/contexts/ImplicitConversionResolutionSuite.scala
@@ -8,8 +8,13 @@ class ImplicitConversionResolutionSuite extends FunSuite:
case class Foo(s: String)
object Foo:
- implicit def fromString(s: String): Foo =
- Foo(s"Foo's implicit conversion: $s")
+ /* DeanW: September 2025. Scala is dropping support for implicit classes,
+ * so we use a given Conversion instead:
+ * implicit def fromString(s: String): Foo =
+ * Foo(s"Foo's implicit conversion: $s")
+ */
+ given Conversion[String, Foo] =
+ s => Foo(s"Foo's implicit conversion: $s")
object scope1:
import Foo.*
@@ -17,8 +22,13 @@ class ImplicitConversionResolutionSuite extends FunSuite:
object scope2:
object implicits:
- implicit def overridingConversion(s: String): Foo =
- Foo(s"scope2's implicit conversion: $s")
+ /* DeanW: September 2025. Scala is dropping support for implicit classes,
+ * so we use a given Conversion instead:
+ * implicit def overridingConversion(s: String): Foo =
+ * Foo(s"scope2's implicit conversion: $s")
+ */
+ given Conversion[String, Foo] =
+ s => Foo(s"scope2's implicit conversion: $s")
def apply(foo: Foo): String = foo.s
@@ -28,7 +38,9 @@ class ImplicitConversionResolutionSuite extends FunSuite:
}
test("The closest implicit conversion in scope is invoked") {
- import scope2.implicits.*
+ // DeanW - September 2025: Now we need to import the given:
+ // import scope2.implicits.*
+ import scope2.implicits.given
assert(scope2(Foo("no use of conversion")) == "no use of conversion")
- assert(scope2("foo") == "scope2's implicit conversion: foo")
+ assert(scope2("foo") == "scope2's implicit conversion: foo", scope2("foobar"))
}
diff --git a/src/test/scala/progscala3/contexts/TypeClassesSubtypingSuite.scala b/src/test/scala/progscala3/contexts/TypeClassesSubtypingSuite.scala
index 261e7c64..a35ae75f 100644
--- a/src/test/scala/progscala3/contexts/TypeClassesSubtypingSuite.scala
+++ b/src/test/scala/progscala3/contexts/TypeClassesSubtypingSuite.scala
@@ -5,10 +5,22 @@ import munit.*
class TypeClassesSubtypingSuite extends FunSuite:
- trait Stringizer[+T]:
- def stringize: String
-
- implicit class AnyStringizer(a: Matchable) extends Stringizer[Matchable]:
+ /* DeanW: September 2025. Scala is dropping support for implicit classes,
+ * so we use an extension method instead:
+ *
+ * trait Stringizer[+T]:
+ * def stringize: String
+ *
+ * implicit class AnyStringizer(a: Matchable) extends Stringizer[Matchable]:
+ * def stringize: String = a match
+ * case s: String => s
+ * case i: Int => (i*10).toString
+ * case f: Float => (f*10).toString
+ * case other =>
+ * throw UnsupportedOperationException(s"Can't stringize $other")
+ */
+
+ extension (a: Matchable)
def stringize: String = a match
case s: String => s
case i: Int => (i*10).toString
diff --git a/src/test/scala/progscala3/contexts/UsingParameterSuite.scala b/src/test/scala/progscala3/contexts/UsingParameterSuite.scala
index 20baf105..19ef69e7 100644
--- a/src/test/scala/progscala3/contexts/UsingParameterSuite.scala
+++ b/src/test/scala/progscala3/contexts/UsingParameterSuite.scala
@@ -7,11 +7,15 @@ class UsingParameterSuite extends FunSuite:
import math.Ordering
case class MySeq[A](seq: Seq[A]):
- def sortBy1[B](f: A => B)(implicit ord: Ordering[B]): Seq[A] =
- seq.sortBy(f)(ord)
+ // 2025-09-20: In Scala 3.7.3, must replace "implicit" with using":
+ // def sortBy1[B](f: A => B)(implicit ord: Ordering[B]): Seq[A] =
+ def sortBy1[B](f: A => B)(using ord: Ordering[B]): Seq[A] =
+ // 2025-06-16: In Scala 3.7, "using" is required in the next line:
+ seq.sortBy(f)(using ord)
def sortBy2[B : Ordering](f: A => B): Seq[A] =
- seq.sortBy(f)(summon[Ordering[B]])
+ // 2025-06-16: In Scala 3.7, "using" is required in the next line:
+ seq.sortBy(f)(using summon[Ordering[B]])
test("A view type can be accessed by implicitly") {
val seq = MySeq(Seq(1,3,5,2,4))
diff --git a/src/test/scala/progscala3/dsls/payroll/PayrollSuite.scala b/src/test/scala/progscala3/dsls/payroll/PayrollSuite.scala
index 0036f988..cd3dd2f8 100644
--- a/src/test/scala/progscala3/dsls/payroll/PayrollSuite.scala
+++ b/src/test/scala/progscala3/dsls/payroll/PayrollSuite.scala
@@ -5,6 +5,7 @@ import progscala3.dsls.payroll.dsc
import progscala3.contexts.accounting.*
import munit.FunSuite
import org.scalacheck.*
+import scala.language.implicitConversions // DeanW: added Sept 2025
/**
* ScalaCheck example driven by MUnit
diff --git a/src/test/scala/progscala3/forcomps/RemoveBlanksSuite.scala b/src/test/scala/progscala3/forcomps/RemoveBlanksSuite.scala
index d615d19d..240b9186 100644
--- a/src/test/scala/progscala3/forcomps/RemoveBlanksSuite.scala
+++ b/src/test/scala/progscala3/forcomps/RemoveBlanksSuite.scala
@@ -3,12 +3,14 @@ package progscala3.forcomps
import munit.*
+import java.lang.System.lineSeparator
+
class RemoveBlanksSuite extends FunSuite:
var path = "src/test/scala/progscala3/forcomps/small-test-file.txt"
test("RemoveBlanks removes blank lines in text") {
val lines = RemoveBlanks(path, compress = false, numbers = false)
- val actual = lines.mkString("\n")
+ val actual = lines.mkString(lineSeparator)
val expected =
""" This is a small
|test file""".stripMargin
@@ -17,7 +19,7 @@ class RemoveBlanksSuite extends FunSuite:
test("RemoveBlanks optionally compresses whitespace in text") {
val lines = RemoveBlanks(path, compress = true, numbers = false)
- val actual = lines.mkString("\n")
+ val actual = lines.mkString(lineSeparator)
val expected =
"""This is a small
|test file""".stripMargin
@@ -26,7 +28,7 @@ class RemoveBlanksSuite extends FunSuite:
test("RemoveBlanks optionally prints line numbers from the original text") {
val lines = RemoveBlanks(path, compress = true, numbers = true)
- val actual = lines.mkString("\n")
+ val actual = lines.mkString(lineSeparator)
val expected =
""" 1: This is a small
| 3: test file""".stripMargin
diff --git a/src/test/scala/progscala3/fp/datastructs/FoldRegexPatternsSuite.scala b/src/test/scala/progscala3/fp/datastructs/FoldRegexPatternsSuite.scala
index 0e7ad3dc..577a4b58 100644
--- a/src/test/scala/progscala3/fp/datastructs/FoldRegexPatternsSuite.scala
+++ b/src/test/scala/progscala3/fp/datastructs/FoldRegexPatternsSuite.scala
@@ -3,6 +3,8 @@ package progscala3.fp.datastructs
import munit.*
+import java.lang.System.lineSeparator
+
class FoldRegexPatternsSuite extends FunSuite:
test("Regex pattern matching used in a foldLeft") {
val ignoreRegex = """^\s*(#.*|\s*)$""".r // <1>
@@ -24,7 +26,7 @@ class FoldRegexPatternsSuite extends FunSuite:
// Parse each line, skipping expected
val actual =
- properties.split("\n").
+ properties.split(lineSeparator).
zipWithIndex.
foldLeft(Vector.empty[Either[Error,KV]]) { case (vect, (line, n)) =>
if ignoreRegex.matches(line) then vect