My favorite new features of Scala 2.13 standard library
A new release of Scala programming language, 2.13, has been announced recently. It is time to look at what improvements it has brought to us. In this post I will focus on the changes in the standard library, so this can be considered a follow up to my previous post, The little gems of Scala standard library. Many of the features described here are not mentioned in the official release notes (where you can find a broad description of general language, collection and compiler improvements).
1. scala.util.Using
This finally gives us an implementation of “Loan” pattern, allowing safe and automatic closing of single (or multiple) resources that are used within some block of code. In this context, a resource
is anything that implements either scala.util.Using.Releasable
or java.lang.AutoCloseable
. Normally you would use it in the same situations as you would use try-with-resources
in Java.
Example:
import scala.util.Using
import java.io.{FileReader, FileWriter}Using.resources(
new FileReader("input.txt"),
new FileWriter("output.txt")) { (reader, writer) =>
???
}
Note: if you are not moving to Scala 2.13 just yet, but would like to have a decent implementation of try-with-resource
in Scala, I suggest reading this useful post by my colleague.
2. minOption / maxOption (also minByOption / maxByOption)
Until 2.13, we had min
/ max
/ minBy
/ maxBy
which could only be used on non empty collections (throwing an UnsupportedOperationException
if the collection was empty). In many cases this was inconvenient and required non-emptiness check before making those calls. The new xxOption
methods are safe to call on any collections, and return a None
in case of an empty input. Also, this makes the API more consistent, since there already has been headOption
and lastOption
for a long time.
3. scala.collection.Stepper
Stepper
is a newer generation of Iterator
abstraction, exposing similar semantics (hasStep
instead of hasNext
, nextStep
instead of next
). Unlike the well known Iterator
, we have specialized variations of it (IntStepper
, LongStepper
, DoubleStepper
) which enable efficient iterating over collections that hold unboxed primitives.
Also, Stepper
provides interoperability with some of the modern features of Java introduced in version 8, such as java.util.Stream
, java.util.PrimitiveIterator
and java.util.Spliterator
.
You can get an instance of Stepper
based on any Scala collection, simply by calling a method .stepper
on it.
4. sizeIs
Some collection types, such as linked List
and BitSet
, do not have an efficient (constant time) way of getting their size. And in many cases, the size itself is not needed, we may only be interested to compare the size to some concrete number (or to the size of another collection). sizeIs
allows doing size comparisons with time complexity O(n min m)
instead of O(n)
, by doing only a limited traversal of the collection (until the given condition can be checked).
Example that validates the size of linked list, without fully traversing it:
val xs = List.fill(5000)(scala.util.Random.nextInt)if (xs.sizeIs > 100) { // traverses no more than 101 element
throw new IllegalArgumentException("Too many numbers!")
}
5. mapInPlace
Supported on some of the mutable collections. Probably relevant if you have performance (or memory) concerns (which is very likely the case, if you have decided to use mutable collections). Updates the original collection in place, instead of allocating a new one (like map
does).
6. minAfter / maxBefore
The new methods minAfter
/ maxBefore
of TreeSet
/ TreeMap
classes support efficient (O(log n)
, thanks to the underlying implementation of RedBlackTree) lookup of the smallest value that is greater or equal (or the greatest value that is less or equal) the given value (same as higher
/ lower
methods of java.util.TreeSet
).
7. scala.collection.mutable.ArrayDeque
An implementation of double-ended queue that provides functionality similar to java.util.ArrayDeque
, just with much richer API, as we are used to have in other Scala collections.
8. Deprecated Map.filterKeys and Map.mapValues
I know, it is strange to have deprecation listed as one of the top features. So let me explain. For whatever historical reasons, these two methods on (otherwise great) implementation of Scala Map
are not strict (meaning that the lambda function passed as an argument to these methods is not being evaluated immediately, but on every access to the resulting Map
instead). And in some cases, such lazy evaluation would actually be useful. The problem is that this is not consistent with other methods of Map
. Also, it is not apparent from method names or signatures (even though return type has been changed from Map
to MapView
in 2.13). As a result, someone using those methods without checking the documentation would risk into getting some unexpected, hard to understand effects. In the worst case, this could cause some side effects (or some expensive calls) to happen more than once. Personally, I have always considered this to be a design issue of Scala Map
. Each time I had to explain this (as in, “the bad parts”) when onboarding a Scala newbie, I felt uncomfortable. So, I’m really happy to see this issue being addressed in the new release.
Many of the features mentioned in this post were not covered by the official release notes. Most likely there still is something more that I have missed. If you discover more interesting “hidden” features of Scala 2.13, please share them in the comments!
P. S. Release notes of Scala 2.13 have been updated to include the changes mentioned in this post. I was really happy to hear that, even though it made some of my statements above no longer accurate 😌.