diff --git a/README.md b/README.md index cb032bba..bb1f4851 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,8 @@ [![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](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), or [LinkedIn](https://www.linkedin.com/in/deanwampler/) accounts. +* [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. @@ -178,28 +177,30 @@ $ 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 [Issues](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). | +| 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". | diff --git a/build.sbt b/build.sbt index 11f8c156..9c49b8ea 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -val scala3 = "3.5.2" +val scala3 = "3.7.1" 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", + "ch.qos.logback" % "logback-classic" % "1.5.18", "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, + "org.scalameta" %% "munit" % "1.1.1" % Test, + "org.scalameta" %% "munit-scalacheck" % "1.1.0" % Test, + "com.eed3si9n.expecty" %% "expecty" % "0.17.0" % Test, ), // For Scala 3 diff --git a/check-mains.sh b/check-mains.sh new file mode 100755 index 00000000..e10f706f --- /dev/null +++ b/check-mains.sh @@ -0,0 +1,239 @@ +#!/usr/bin/env bash + +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 true 10" + 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 +) +expected_errors_in=() + +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 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 [[ $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 $@" > "$out" + else + $NOOP mkdir -p $(dirname $out) + $NOOP "TERM=dumb sbt runMain $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..59ad7d2b 100755 --- a/check-scripts.sh +++ b/check-scripts.sh @@ -3,6 +3,50 @@ 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/MatchableOpaque.scala + src/script/scala/progscala3/collections/MultiMap.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/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 +65,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 +121,10 @@ do -v|--v*) VERBOSE=true ;; - -c|--c*) + --check*) + CHECK_ONLY=true + ;; + -c|--cl*) CLEAN=true ;; -n|--n*) @@ -71,7 +141,7 @@ do done [[ ${#dirs[@]} -gt 0 ]] || dirs=( ${default_dirs[@]} ) -$VERBOSE && echo "Reading directories ${dirs[@]}" +$VERBOSE && echo "Reading directories: ${dirs[@]}" if $CLEAN then @@ -79,27 +149,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 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 [[ $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..bbb0b608 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.4 +sbt.version=1.11.2 diff --git a/project/plugins.sbt b/project/plugins.sbt index 28d7630d..170c1877 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.3.1") 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..4d2f5a2a 100644 --- a/src/main/scala/progscala3/basicoop/tagging/Tags2.scala +++ b/src/main/scala/progscala3/basicoop/tagging/Tags2.scala @@ -60,7 +60,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/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/dsls/payroll/Money.scala b/src/main/scala/progscala3/dsls/payroll/Money.scala index 3e2bdceb..fbc8ea70 100644 --- a/src/main/scala/progscala3/dsls/payroll/Money.scala +++ b/src/main/scala/progscala3/dsls/payroll/Money.scala @@ -3,10 +3,10 @@ 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> 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/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..05ea3099 100644 --- a/src/script/scala/progscala3/BracesSyntax.scala +++ b/src/script/scala/progscala3/BracesSyntax.scala @@ -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..42d5aaac 100644 --- a/src/script/scala/progscala3/IndentationSyntax.scala +++ b/src/script/scala/progscala3/IndentationSyntax.scala @@ -52,7 +52,7 @@ end while // Match expression 0 match case 0 => println("zero") - case _ => println("other value") + case _ => println("other value") // ERROR: "Match case Unreachable Warning" end match // Partially-defined function @@ -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..1b1015a9 100644 --- a/src/script/scala/progscala3/basicoop/tagging/Tags.scala +++ b/src/script/scala/progscala3/basicoop/tagging/Tags.scala @@ -14,5 +14,5 @@ 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! +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..604d3cfb 100644 --- a/src/script/scala/progscala3/basicoop/tagging/Tags2.scala +++ b/src/script/scala/progscala3/basicoop/tagging/Tags2.scala @@ -14,5 +14,5 @@ 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! +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..6348fa45 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[] @@ -45,7 +45,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/UsingParameterSuite.scala b/src/test/scala/progscala3/contexts/UsingParameterSuite.scala index 20baf105..c0e2f002 100644 --- a/src/test/scala/progscala3/contexts/UsingParameterSuite.scala +++ b/src/test/scala/progscala3/contexts/UsingParameterSuite.scala @@ -8,10 +8,12 @@ class UsingParameterSuite extends FunSuite: case class MySeq[A](seq: Seq[A]): def sortBy1[B](f: A => B)(implicit ord: Ordering[B]): Seq[A] = - seq.sortBy(f)(ord) + // 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/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