I’m bringing the Scala track’s practice exercises in line with problem-specifications (exercism/scala#488): the test suites are generated from the canonical data, and the code is written in Scala 3. Most exercises only need the missing cases added. A few don’t fit the track’s API, so I split them out into their own issues and checked how the Java and Kotlin tracks handled them.
The guiding idea for these is to follow what is idiomatic in Scala, rather than copy the JVM neighbours one-to-one: errors are values (Either/Option) rather than exceptions, data structures are immutable, and Scala 3 features are used where they fit. Below are the decisions I’m planning to make.
Decisions
protein-translation (exercism/scala#947): three canonical cases expect an “Invalid codon” error, and the track has no way to report one. Java and Kotlin keep the list result and throw IllegalArgumentException("Invalid codon"). In Scala I’m changing the result to Either[String, Seq[String]], with Left("Invalid codon"). That keeps the canonical message, and an error is a value the caller has to handle. It changes the signature, so existing solutions will need updating.
reverse-string (exercism/scala#948): Java excludes all three Unicode cases as out of scope. In Scala, “wide characters” and “grapheme cluster with pre-combined form” already pass with String#reverse, so I’m including them. I’m excluding only “grapheme clusters”: it needs grapheme segmentation (java.text.BreakIterator), which is too much for an introductory exercise.
bank-account (exercism/scala#949): our track had its own API (incrementBalance, everything returns Option), and it can’t express the canonical cases. Like Java, I’m adopting the canonical operations: open, close, balance, deposit and withdraw. Instead of Java’s checked exception, each one returns an Either[String, ...] with the canonical message (“account not open”, “amount must be greater than 0”, …). The concurrency test stays, because that is what the exercise is about on the JVM.
simple-linked-list (exercism/scala#950): the canonical data now describes a stack with push, pop, peek, reverse, toList and count. Java implements it as a mutable class. In Scala I’m keeping it immutable, as Scala’s own List is:
pushreturns a new list;popreturnsOption[(T, SimpleLinkedList[T])];peekreturnsOption[T].
An empty list has only one way to fail, so Option is enough here, as in List#headOption. Like Java, I’m excluding the “toList FIFO” cases.
complex-numbers (exercism/scala#951): eight canonical cases mix real and complex numbers, such as (1 + 2i) + 5 and 5 + (1 + 2i). Java excludes all eight (“the Java exercise only focuses on operations between two complex numbers”). Scala 3 can support them idiomatically, so I’m including them:
- overloads on
ComplexNumberhandle a real number on the right; - extension methods such as
extension (d: Double) def +(z: ComplexNumber)handle a real number on the left.
This makes the exercise a natural place to meet extension methods. Existing solutions will need the new operators.
Timing
I’ll leave these five exercises for now and finish the rest of the track first. If nobody objects by then, I’ll go ahead as described above. Comments and objections are very welcome, on any of the five.