From ES6 to Scala: Collections
In JavaScript there are basically two kinds of collections you have used to store your data: the Array
for sequential
data and Object
(aka dictionary or hash map) for storing key-value pairs. Furthermore both of these are mutable by
default, so if you pass them to a function, that function might go and modify them without your knowledge.
ES6 extends your options with four new collection types Map
, Set
, WeakMap
and
WeakSet
. Of these the WeakMap
and WeakSet
are for special purposes only, so in your application you
would typically use only Map
and Set
.
Scala collection hierarchy
Unlike JavaScript, the Scala standard library has a huge variety of different collection types to choose from. Furthermore the collections are organized in a type hierarchy, meaning they share a lot of common functionality and interfaces. The high-level hierarchy for the abstract base classes and traits is shown in the image below.
Scala provides immutable and mutable implementations for all these collection types.
Common immutable collections | |
---|---|
Seq | List , Vector , Stream , Range |
Map | HashMap , TreeMap |
Set | HashSet , TreeSet |
Common mutable collections | |
Seq | Buffer , ListBuffer , Queue , Stack |
Map | HashMap , LinkedHashMap |
Set | HashSet |
Comparing to JavaScript
Let’s start with familiar things and see how Scala collections compare with the JavaScript Array
and Object
(or
Map
). The closest match for Array
would be the mutable Buffer
since arrays in Scala cannot change size after
initialization. For Object
(or Map
) the best match is the mutable HashMap
.
A simple example of array manipulation.
ES6
Scala
Working with a hash map (or Object).
ES6
Scala
Even though you can use Scala collections like you would use arrays and objects in JavaScript, you really shouldn’t, because you are missing a lot of great functionality.
Common collections Seq
, Map
, Set
and Tuple
For 99% of the time you will be working with those four common collection types in your code. You will instantiate
implementation collections like Vector
or HashMap
, but in your code you don’t really care what the implementation is,
as long as it behaves like a Seq
or a Map
.
Tuple
You may have noticed that Tuple
is not shown in the collection hierarchy above, because it’s a very specific
collection type of its own. Scala tuple combines a fixed number of items together so that they can be passed around as a
whole. A tuple is immutable and can hold different types, so it’s quite close to an anonymous case class in that sense.
Tuples are used in situations where you need to group items together, like key and value in a map, or to return multiple
values. In JavaScript you can use a fixed size array to represent a tuple.
ES6
Scala
To access values inside a tuple, use the tuple._1
syntax, where the number indicates position within the tuple
(starting from 1, not 0). Quite often you can also use destructuring to extract the values.
ES6
Scala
Seq
Seq
is an ordered sequence. Typical implementations include List
, Vector
, Buffer
and Range
. Although Scala
Array
is not a Seq
, it can be wrapped into a WrappedArray
to enable all Seq
operations on arrays. In Scala this is done automatically through an implicit conversion, allowing you to write code
like following.
Scala
The Seq
trait exposes many methods familiar to the users of JavaScript arrays, including
foreach
, map
,
filter
, slice
and reverse
. In addition to these, there are several more useful methods
shown with examples in the code block below.
Scala
The functionality offered by Array.reduce
in JavaScript
is covered by two distinct methods in Scala: reduceLeft
and foldLeft
. The difference is that in foldLeft
you provide an initial (“zero”) value (which is an optional parameter to Array.reduce
) while in reduceLeft
you don’t.
Also note that in foldLeft
, the type of the accumulator can be something else, for example a tuple, but in reduceLeft
it must always be a supertype of the value.
Since reduceLeft
cannot deal with an empty collection, it is rarely useful.
ES6
Scala
Map
A Map
consists of pairs of keys and values. Both keys and values can be of any valid Scala type, unlike in JavaScript
where an Object
may only contain string
or symbol
keys (the new ES6 Map
allows using other types as keys, but supports only
referential equality for comparing keys).
JavaScript Object
doesn’t really have methods for using it as a map, although you can iterate over the keys
with Object.keys
. When using Object
as a map, most developers use utility libraries like
lodash to get access to suitable functionality. The ES6 Map
object contains
keys
, values
and forEach
methods for accessing its contents, but all
transformation methods are missing.
You can build a map directly or from a sequence of key-value pairs.
ES6
Scala
In Scala when a function expects a variable number of parameters (like the Map
constructor), you can destructure a
sequence with the seq:_*
syntax, which is the equivalent of ES6’s spread operator ...seq
.
Accessing Map
contents can be done in many ways.
ES6
Scala
In the previous example m.get("first")
returns an Option[String]
indicating whether the key is present in the map
or not. By using a for comprehension, we can easily extract three separate values from the map and use them to build the
result. The result from for {} yield
is also an Option[String]
so we can use getOrElse
to provide a default value.
Let’s try something more complicated. Say we need to maintain a collection of players and all their game scores. This
could be represented by a Map[String, Seq[Int]]
ES6
Scala
In the example above the both versions are using mutable collections. Coming from JavaScript it’s good to start with the more familiar mutable collections, but over time Scala developers tend to favor immutable versions. Immutable collections in Scala use structural sharing to minimize copying and to provide good performance. Sharing is ok, because the data is immutable!
The best score is found by first flattening the whole structure into a sequence of (player, score) pairs. Then we use
the maxBy
method to find the maximum score by looking at the second
value in the tuple.
The average is calculated simply by flattening all scores into a single sequence and then calculating its average.
Set
A Set
is like a Map
without values, just the distinct keys. In JavaScript it’s typical to
emulate a Set by storing the values as keys into an Object
. This of course means that the values must be converted to
strings. In ES6 there is a new Set
type that works with all kinds of value types, but like with Map
, it’s
based on reference equality, making it less useful when dealing with complex value types.
As their name implies, sets have no duplicate elements. Adding values to a set automatically guarantees that all duplicate values are eliminated.
Set operations like diff
,
intersect
and
union
allow you to build new sets out of other
sets to check, for example, what has changed.
Scala
Note how in Scala you can also omit the .
and parentheses in method calls.
Sets are also a convenient way to check for multiple values in methods like filter
.
ES6
Scala
Next, let’s look at some more advanced paradigms and features of Scala.