In JavaScript there are basically two kinds of collections you have used to store your data: the Array for sequentialdata and Object (aka dictionary or hash map) for storing key-value pairs. Furthermore both of these are mutable bydefault, 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 andWeakSet. Of these the WeakMap and WeakSet are for special purposes only, so in your application youwould 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 andinterfaces. The high-level hierarchy for the abstract base classes and traits is shown in the image below.

Scala collection hierarchy

Scala provides immutable and mutable implementations for all these collection types.

Common immutable collections
SeqList, Vector, Stream, Range
MapHashMap, TreeMap
SetHashSet, TreeSet
Common mutable collections
SeqBuffer, ListBuffer, Queue, Stack
MapHashMap, LinkedHashMap
SetHashSet

Comparing to JavaScript

Let’s start with familiar things and see how Scala collections compare with the JavaScript Array and Object (orMap). The closest match for Array would be the mutable Buffer since arrays in Scala cannot change size afterinitialization. For Object (or Map) the best match is the mutable HashMap.

A simple example of array manipulation.

ES6
  1. const a = ["Fox", "jumped", "over"];
  2. a.push("me"); // Fox jumped over me
  3. a.unshift("Red"); // Red Fox jumped over me
  4. const fox = a[1];
  5. a[a.length - 1] = "you"; // Red Fox jumped over you
  6. console.log(a.join(" "));
Scala
  1. import scala.collection.mutable
  2. val a = mutable.Buffer("Fox", "jumped", "over")
  3. a.append("me") // Fox jumped over me
  4. a.prepend("Red") // Red Fox jumped over me
  5. val fox = a(1)
  6. a(a.length - 1) = "you" // Red Fox jumped over you
  7. println(a.mkString(" "))

Working with a hash map (or Object).

ES6
  1. const p = {first: "James", last: "Bond"};
  2. p["profession"] = "Spy";
  3. const name = `${p.first} ${p.last}`
Scala
  1. import scala.collection.mutable
  2. val p = mutable.HashMap("first" -> "James",
  3. "last" -> "Bond")
  4. p("profession") = "Spy"
  5. val name = s"${p("first")} ${p("last")}"

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 instantiateimplementation 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 specificcollection type of its own. Scala tuple combines a fixed number of items together so that they can be passed around as awhole. 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 multiplevalues. In JavaScript you can use a fixed size array to represent a tuple.

ES6
  1. const t = ["James", "Bond", 42];
  2. const kv = ["key", 42];
  3. function sumProduct(s) {
  4. let sum = 0;
  5. let product = 1;
  6. for(let i of s) {
  7. sum += i;
  8. product *= i;
  9. }
  10. return [sum, product];
  11. }
Scala
  1. val t = ("James", "Bond", 42)
  2. val kv = "key" -> 42 // same as ("key", 42)
  3. def sumProduct(s: Seq[Int]): (Int, Int) = {
  4. var sum = 0
  5. var product = 1
  6. for(i <- s) {
  7. sum += i
  8. product *= i
  9. }
  10. (sum, product)
  11. }

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
  1. const sc = sumProduct([1, 2, 3]);
  2. const sum = sc[0];
  3. const product = sc[1];
  4. // with destructuring
  5. const [sum, product] = sumProduct([1, 2, 3]);
Scala
  1. val sc = sumProduct(Seq(1, 2, 3))
  2. val sum = sc._1
  3. val product = sc._2
  4. // with destructuring
  5. val (sum, product) = sumProduct(Seq(1, 2, 3))

Seq

Seq is an ordered sequence. Typical implementations include List, Vector, Buffer and Range. Although ScalaArray is not a Seq, it can be wrapped into a WrappedArray to enable all Seqoperations on arrays. In Scala this is done automatically through an implicit conversion, allowing you to write codelike following.

Scala
  1. val ar = Array(1, 2, 3, 4)
  2. val product = ar.foldLeft(1)((a, x) => a * x) // foldLeft comes from WrappedArray

The Seq trait exposes many methods familiar to the users of JavaScript arrays, includingforeach:Unit), map:Seq[B]),filter:Repr), slice:Repr)and reverse. In addition to these, there are several more useful methodsshown with examples in the code block below.

Scala
  1. val seq = Seq(1, 2, 3, 4, 5)
  2. seq.isEmpty == false
  3. seq.contains(6) == false // JS Array.indexOf(6) == -1
  4. seq.forall(x => x > 0) == true // JS Array.every()
  5. seq.exists(x => x % 3 == 0) == true // JS Array.some()
  6. seq.find(x => x > 3) == Some(4) // JS Array.find()
  7. seq.head == 1
  8. seq.tail == Seq(2, 3, 4, 5)
  9. seq.last == 5
  10. seq.init == Seq(1, 2, 3, 4)
  11. seq.drop(2) == Seq(3, 4, 5)
  12. seq.dropRight(2) == Seq(1, 2, 3)
  13. seq.count(x => x < 3) == 2
  14. seq.groupBy(x => x % 2) == Map(1 -> Seq(1, 3, 5), 0 -> Seq(2, 4))
  15. seq.sortBy(x => -x) == Seq(5, 4, 3, 2, 1)
  16. seq.partition(x => x > 3) == (Seq(4, 5), Seq(1, 2, 3))
  17. seq :+ 6 == Seq(1, 2, 3, 4, 5, 6)
  18. seq ++ Seq(6, 7) == Seq(1, 2, 3, 4, 5, 6, 7) // JS Array.concat()

The functionality offered by Array.reduce in JavaScriptis covered by two distinct methods in Scala: reduceLeft=>B):B)and foldLeft(op:(B,A)=>B):B). The difference is that in foldLeftyou 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 reduceLeftit must always be a supertype of the value.Since reduceLeft cannot deal with an empty collection, it is rarely useful.

ES6
  1. function sumProduct(s) {
  2. // destructuring works in the function argument
  3. return s.reduce(([sum, product], x) =>
  4. [sum + x, product * x],
  5. [0, 1] // use an array to represent a tuple
  6. );
  7. }
Scala
  1. def sumProduct(s: Seq[Int]): (Int, Int) = {
  2. // use a tuple accumulator to hold sum and product
  3. s.foldLeft((0, 1)) { case ((sum, product), x) =>
  4. (sum + x, product * x)
  5. }
  6. }

Map

A Map consists of pairs of keys and values. Both keys and values can be of any valid Scala type, unlike in JavaScriptwhere an Object may only contain string keys (the new ES6 Map allows using other types as keys, but supports onlyreferential equality for comparing keys).

JavaScript Object doesn’t really have methods for using it as a map, although you can iterate over the keyswith Object.keys. When using Object as a map, most developers use utility libraries likelodash to get access to suitable functionality. The ES6 Map object containskeys, values and forEach methods for accessing its contents, but alltransformation methods are missing.

You can build a map directly or from a sequence of key-value pairs.

ES6
  1. // object style map
  2. const m = {first: "James", last: "Bond"};
  3. // ES6 Map
  4. const data = [["first", "James"], ["last", "Bond"]];
  5. const m2 = new Map(data);
Scala
  1. val m = Map("first" -> "James", "last" -> "Bond")
  2. val data = Seq("first" -> "James", "last" -> "Bond")
  3. val m2 = Map(data:_*)

In Scala when a function expects a variable number of parameters (like the Map constructor), you can destructure asequence with the seq:* syntax, which is the equivalent of ES6’s _spread operator …seq.

Accessing Map contents can be done in many ways.

ES6
  1. // object syntax
  2. const name = `${m.last}, ${m.first} ${m.last}`
  3. // ES6 Map syntax
  4. const name2 = `${m2.get("last")}, ${m2.get("first")} ${m2.get("last")}`
  5. // use default value when missing
  6. const age = m.age === undefined ? "42" : m.age;
  7. // check all fields are present
  8. const person = m.first !== undefined &&
  9. m.last !== undefined &&
  10. m.age !== undefined ? `${m.last}, ${m.first}: ${m.age}` :
  11. "missing";
Scala
  1. val name = s"${m("last")}, ${m("first")} ${m("last")}"
  2. // use default value when missing
  3. val age = m.getOrElse("age", "42")
  4. // check all fields are present
  5. val person = (for {
  6. first <- m.get("first")
  7. last <- m.get("last")
  8. age <- m.get("age")
  9. } yield {
  10. s"$last, $first: $age"
  11. }).getOrElse("missing")

In the previous example m.get("first") returns an Option[String] indicating whether the key is present in the mapor not. By using a for comprehension, we can easily extract three separate values from the map and use them to build theresult. The result from for {} yield is also an Option[String] so we can use getOrElse:B)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. Thiscould be represented by a Map[String, Seq[Int]]

ES6
  1. const scores = {};
  2. function addScore(player, score) {
  3. if (scores[player] === undefined)
  4. scores[player] = [];
  5. scores[player].push(score);
  6. }
  7. function bestScore() {
  8. let bestScore = 0;
  9. let bestPlayer = "";
  10. for (let player in scores) {
  11. const max = scores[player].reduce((a, score) =>
  12. Math.max(score, a)
  13. );
  14. if (max > bestScore) {
  15. bestScore = max;
  16. bestPlayer = player;
  17. }
  18. }
  19. return [bestPlayer, bestScore];
  20. }
  21. function averageScore() {
  22. let sum = 0;
  23. let count = 0;
  24. for (let player in scores) {
  25. for (let score of scores[player]) {
  26. sum += score;
  27. count++;
  28. }
  29. }
  30. if (count == 0)
  31. return 0;
  32. else
  33. return Math.round(sum / count);
  34. }
Scala
  1. import scala.collection.mutable
  2. val scores =
  3. mutable.Map.empty[String, mutable.Buffer[Int]]
  4. def addScore(player: String, score: Int): Unit = {
  5. scores.getOrElseUpdate(player, mutable.Buffer())
  6. .append(score)
  7. }
  8. def bestScore: (String, Int) = {
  9. val all = scores.toList.flatMap {
  10. case (player, pScores) =>
  11. pScores.map(s => (player, s))
  12. }
  13. if (all.isEmpty)
  14. ("", 0)
  15. else
  16. all.maxBy(_._2)
  17. }
  18. def averageScore: Int = {
  19. val allScores = scores.flatMap(_._2)
  20. if (allScores.isEmpty)
  21. 0
  22. else
  23. allScores.sum / allScores.size
  24. }

In the example above the both versions are using mutable collections. Coming from JavaScript it’s good to start with themore familiar mutable collections, but over time Scala developers tend to favor immutable versions. Immutablecollections in Scala use structural sharing to minimize copying and to provide good performance. Sharing is ok, becausethe data is immutable!

The best score is found by first flattening the whole structure into a sequence of (player, score) pairs. Then we usethe maxBy:A) method to find the maximum score by looking at the secondvalue 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 toemulate a Set by storing the values as keys into an Object. This of course means that the values must be converted tostrings. In ES6 there is a new Set type that works with all kinds of value types, but like with Map, it’sbased 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 setautomatically guarantees that all duplicate values are eliminated.

Set operations like diff:This),intersect:Repr) andunion:This) allow you to build new sets out of othersets to check, for example, what has changed.

Scala
  1. val set1 = Set(1, 2, 3, 4, 5)
  2. val set2 = Set(2, 3, 5, 1, 6)
  3. val addedValues = set2 diff set1 // Set(6)
  4. val removedValues = set1 diff set2 // Set(4)

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
  1. const common = {"a": true, "the": true,
  2. "an": true, "and": true};
  3. const text = "The sun is a star and an energy source"
  4. const words = text.split(" ")
  5. .map(s => s.toLowerCase())
  6. .filter(s => !common[s]);
Scala
  1. val common = Set("a", "the", "an", "and")
  2. val text = "The sun is a star and an energy source"
  3. val words = text.split(" ")
  4. .map(_.toLowerCase)
  5. .filterNot(common)
  6. // Array(sun, is, star, energy, source)

Next, let’s look at some more advanced paradigms and features of Scala.