Scala is a feature rich language that is easy to learn but takes time to master. Depending on your programmingbackground, typically you start by writing Scala as you would’ve written the language you know best (JavaScript, Java orC# for example) and gradually learn more and more idiomatic Scala paradigms to use. In this section we cover some of themore useful design patterns and features, to get you started quickly.

Pattern matching

In the Basics part we already saw simple examples of pattern matching as a replacement for JavaScript’s switchstatement. However, it can be used for much more, for example checking the type of input.

ES6

  1. function printType(o) {
  2. switch (typeof o) {
  3. case "string":
  4. console.log(`It's a string: ${o}`);
  5. break;
  6. case "number":
  7. console.log(`It's a number: ${o}`);
  8. break;
  9. case "boolean":
  10. console.log(`It's a boolean: ${o}`);
  11. break;
  12. default:
  13. console.log(`It's something else`);
  14. }
  15. }

Scala

  1. def printType(o: Any): Unit = {
  2. o match {
  3. case s: String =>
  4. println(s"It's a string: $s")
  5. case i: Int =>
  6. println(s"It's an int: $i")
  7. case b: Boolean =>
  8. println(s"It's a boolean: $b")
  9. case _ =>
  10. println("It's something else")
  11. }

Pattern matching uses something called partial functions which means it can be used in place of regular functions, forexample in a call to filter or map. You can also add a guard clause in the form of an if, to limit the match. Ifyou need to match to a variable, use backticks to indicate that.

ES6

  1. function parse(str, magicKey) {
  2. let res = [];
  3. for(let c of str) {
  4. if (c === magicKey)
  5. res.push("magic");
  6. else if (c.match(/\d/))
  7. res.push("digit");
  8. else if (c.match(/\w/))
  9. res.push("letter");
  10. else if (c.match(/\s/))
  11. res.push(" ");
  12. else
  13. res.push("char");
  14. }
  15. return res;
  16. }
  17. const r = parse("JB/007", '/');
  18. // [letter, letter, magic, digit, digit, digit]

Scala

  1. def parse(str: String, magicKey: Char): Seq[String] = {
  2. str.map {
  3. case c if c == magicKey =>
  4. "magic"
  5. case c if c.isDigit =>
  6. "digit"
  7. case c if c.isLetter =>
  8. "letter"
  9. case c if c.isWhitespace =>
  10. " "
  11. case c =>
  12. "char"
  13. }
  14. }
  15. val r = parse("JB/007", '/')
  16. // Seq(letter, letter, magic, digit, digit, digit)

Destructuring

Where pattern matching really shines is at destructuring. This means matching to a more complex pattern and extractingvalues inside that structure. ES6 also supports destructuring (yay!) in assignments and function parameters, but notin matching.

ES6

  1. const person = {first: "James", last: "Bond", age: 42};
  2. const {first, last, age: years} = person;
  3. // first = "James", last = "Bond", years = 42
  4. const seq = [1, 2, 3, 4, 5];
  5. const [a, b, , ...c] = seq;
  6. // a = 1, b = 2, c = [4, 5]
  7. const seq2 = [a, b].concat(c); // [1, 2, 4, 5]

Scala

  1. case class Person(first: String, last: String, age: Int)
  2. val person = Person("James", "Bond", 42)
  3. val Person(first, last, years) = person
  4. // first = "James", last = "Bond", years = 42
  5. val seq = Seq(1, 2, 3, 4, 5)
  6. val Seq(a, b, _, c @ _*) = seq
  7. // a = 1, b = 2, c = Seq(4, 5)
  8. val seq2 = Seq(a, b) ++ c // Seq(1, 2, 4, 5)

In Scala the destructuring and rebuilding have nice symmetry making it easy to remember how to do it. Use _ to skipvalues in destructuring.

In pattern matching the use of destructuring results in clean, simple and understandable code.

ES6

  1. function ageSum(persons, family) {
  2. return persons.filter(p => p.last === family)
  3. .reduce((a, p) => a + p.age, 0);
  4. }
  5. const persons = [
  6. {first: "James", last: "Bond", age: 42},
  7. {first: "Hillary", last: "Bond", age: 35},
  8. {first: "James", last: "Smith", age: 55}
  9. ];
  10. ageSum(persons, "Bond") == 77;

Scala

  1. def ageSum(persons: Seq[Person],
  2. family: String): Int = {
  3. persons.collect {
  4. case Person(_, last, age) if last == family =>
  5. age
  6. }.sum
  7. }
  8. val persons = Seq(
  9. Person("James", "Bond", 42),
  10. Person("Hillary", "Bond", 35),
  11. Person("James", "Smith", 55)
  12. )
  13. ageSum(persons, "Bond") == 77

We could’ve implemented the Scala function using a filter and foldLeft, but it is more understandable usingcollect:Seq[B]) and pattern matching. It would be readas “Collect every person with a last name equaling family and extract the age of those persons. Then sum up the ages.”

Another good use case for pattern matching is regular expressions (also in ES6!). Let’s extract a date in differentformats.

ES6

  1. function convertToDate(d) {
  2. const YMD = /(\d{4})-(\d{1,2})-(\d{1,2})/
  3. const MDY = /(\d{1,2})\/(\d{1,2})\/(\d{4})/
  4. const DMY = /(\d{1,2})\.(\d{1,2})\.(\d{4})/
  5. const [, year, month, day] = YMD.exec(d) || [];
  6. if (year !== undefined) {
  7. return {
  8. year: parseInt(year),
  9. month: parseInt(month),
  10. day: parseInt(day)
  11. };
  12. } else {
  13. const [, month, day, year] = MDY.exec(d) || [];
  14. if (year !== undefined) {
  15. return {
  16. year: parseInt(year),
  17. month: parseInt(month),
  18. day: parseInt(day)
  19. };
  20. } else {
  21. const [, day, month, year] = DMY.exec(d) || [];
  22. if (year !== undefined) {
  23. return {
  24. year: parseInt(year),
  25. month: parseInt(month),
  26. day: parseInt(day)
  27. };
  28. }
  29. }
  30. }
  31. throw new Error("Invalid date!");
  32. }
  33. convertToDate("2015-10-9"); //{year:2015,month:10,day:9}
  34. convertToDate("10/9/2015"); //{year:2015,month:10,day:9}
  35. convertToDate("9.10.2015"); //{year:2015,month:10,day:9}
  36. convertToDate("10 Nov 2015"); // exception

Scala

  1. case class Date(year: Int, month: Int, day: Int)
  2. def convertToDate(d: String): Date = {
  3. val YMD = """(\d{4})-(\d{1,2})-(\d{1,2})""".r
  4. val MDY = """(\d{1,2})/(\d{1,2})/(\d{4})""".r
  5. val DMY = """(\d{1,2})\.(\d{1,2})\.(\d{4})""".r
  6. d match {
  7. case YMD(year, month, day) =>
  8. Date(year.toInt, month.toInt, day.toInt)
  9. case MDY(month, day, year) =>
  10. Date(year.toInt, month.toInt, day.toInt)
  11. case DMY(day, month, year) =>
  12. Date(year.toInt, month.toInt, day.toInt)
  13. case _ =>
  14. throw new Exception("Invalid date!")
  15. }
  16. }
  17. convertToDate("2015-10-9") // = Date(2015,10,9)
  18. convertToDate("10/9/2015") // = Date(2015,10,9)
  19. convertToDate("9.10.2015") // = Date(2015,10,9)
  20. convertToDate("10 Nov 2015") // exception

Here we use triple-quoted strings that allow us to write regex without escaping special characters. The string isconverted into a Regex object with the .r method. Because regexes extract strings, we needto convert matched groups to integers ourselves.

Functions revisited

We covered the basic use functions in Part 1, but Scala, being a functional programminglanguage, provides much more when it comes to functions. Let’s explore some of the more advanced features and how theycompare to JavaScript.

Higher-order functions

Scala, as JavaScript, allows the definition of higher-order functions. These are functions that take other functions asparameters, or whose result is a function. Higher-order functions should be familiar to JavaScript developers, becausethey often appear in form of functions that take callbacks as parameters.

Typically higher-order functions are used to pass specific functionality to a general function, like in the case ofArray.prototype.filter in ES6 or Seq.filter in Scala. We can use this to build a function to calculate a minimum andmaximum from a sequence of values, using a function to extract the target value.

ES6

  1. function minmaxBy(arr, f) {
  2. return arr.reduce(
  3. ([min, max], e) => {
  4. const v = f(e);
  5. return [Math.min(min, v), Math.max(max, v)]
  6. },
  7. [Number.MAX_VALUE, Number.MIN_VALUE]
  8. )
  9. }
  10. const [youngest, oldest] = minmaxBy(persons, e => e.age);

Scala

  1. def minmaxBy[T](seq: Seq[T], f: T => Int): (Int, Int) = {
  2. seq.foldLeft((Int.MaxValue, Int.MinValue)) {
  3. case ((min, max), e) =>
  4. val v = f(e)
  5. (math.min(min, v), math.max(max, v))
  6. }
  7. }
  8. val (youngest, oldest) = minmaxBy[Person](persons, _.age)

Call-by-Name

In some cases you want to defer the evaluation of a parameter value until when it’s actually used in the function. Forthis purpose Scala offers call-by-name parameters. This can be useful when dealing with an expensive computation thatis only optionally used by the function. In JavaScript the closest thing to this is to wrap a value in an anonymousfunction with no arguments and pass that as a parameter, but that’s more verbose and error-prone. You need to rememberto both wrap the value and call the function.

ES6

  1. function compute(value, cPos, cNeg) {
  2. if (value >= 0)
  3. return cPos();
  4. else
  5. return cNeg();
  6. }
  7. compute(x, () => expCalc(), () => expCalc2());

Scala

  1. def compute(value: Int, cPos: => Int, cNeg: => Int) = {
  2. if (value >= 0)
  3. cPos
  4. else
  5. cNeg
  6. }
  7. compute(x, expCalc, expCalc2)

Recursive functions

Recursive functions can be very expressive, but they may also cause spurious stack overflows if the recursion gets toodeep. Scala automatically optimizes recursive functions that are tail recursive, allowing you to use them withoutfear of overflowing the stack. To make sure your function is actually tail recursive, use the @tailrec annotation,which will cause the Scala compiler to report an error if your function is not tail recursive.

Before ES6, JavaScript did not support tail call optimization, nor optimizing tail recursive functions. If you use asmart ES6 transpiler, it can actually convert a tail recursive function into a while loop, but there are no checksavailable to help you to verify the validity of tail recursion.

ES6

  1. function fib(n) {
  2. function fibIter(n, next, prev) {
  3. if (n === 0) {
  4. return prev;
  5. } else {
  6. return fibIter(n - 1, next + prev, next);
  7. }
  8. };
  9. return fibIter(n, 1, 0);
  10. }

Scala

  1. def fib(n: Int): Int = {
  2. @tailrec
  3. def fibIter(n: Int, next: Int, prev: Int): Int = {
  4. if (n == 0)
  5. prev
  6. else
  7. fibIter(n - 1, next + prev, next)
  8. }
  9. fibIter(n, 1, 0)
  10. }

Partially applied functions

In Scala you can call a function with only some of its arguments and get back a function taking those missing arguments.You do this by using _ in place of the actual parameter. In JavaScript you can achieve the same by using theFunction.prototype.bind function (although it limits you to providing parameters from left to right). For example wecan define a function to create HTML tags by wrapping content within start and end tags.

ES6

  1. function tag(name, content) {
  2. return `<${name}>${content}</${name}>`
  3. }
  4. const div = tag.bind(null, "div");
  5. const p = tag.bind(null, "p");
  6. const html = div(p("test")); // <div><p>test</p></div>

Scala

  1. def tag(name: String, content: String) = {
  2. s"<$name>$content</$name>"
  3. }
  4. val div = tag("div", _: String)
  5. val p = tag("p", _: String)
  6. val html = div(p("test")) // <div><p>test</p></div>

Multiple parameter lists

Scala allows a function to be defined with multiple parameter lists. In Scala this is quite common as it provides somepowerful secondary benefits besides the usual currying functionality. JavaScript does not directly support multipleparameter lists in its syntax, but you can emulate it by returning a chain of functions, or by using libraries likelodash that do it for you.

Let’s use currying to define the tag function from previous example.

ES6

  1. function tag(name) {
  2. return (content) => `<${name}>${content}</${name}>`;
  3. }
  4. const div = tag("div");
  5. const p = tag("p");
  6. const html = div(p("test")); // <div><p>test</p></div>

Scala

  1. def tag(name: String)(content: String): String = {
  2. s"<$name>$content</$name>"
  3. }
  4. val div = tag("div") _
  5. val p = tag("p") _
  6. val html = div(p("test")) // <div><p>test</p></div>

Multiple parameter lists also helps with type inference, meaning we don’t need to tell the compiler the typesexplicitly. For example we can rewrite the minmaxBy function as curried, which allows us to leave the Person typeout when calling it, as it is automatically inferred from the first parameter. This is why methods like foldLeft aredefined with multiple parameter lists.

Scala

  1. def minmaxBy[T](seq: Seq[T])(f: T => Int): (Int, Int) = {
  2. seq.foldLeft((Int.MaxValue, Int.MinValue)) {
  3. case ((min, max), e) =>
  4. val v = f(e)
  5. (math.min(min, v), math.max(max, v))
  6. }
  7. }
  8. val (youngest, oldest) = minmaxBy(persons)(_.age)

Implicits

Being type safe is great in Scala, but sometimes the type system can be a bit prohibitive when you want to do somethingelse, like add methods to existing classes. To allow you to do this in a type safe manner, Scala provides implicits.You can think of implicits as something that’s available in the scope when you need it, and the compiler canautomatically provide it. For example we can provide a function to automatically convert a JavaScript Dateinto a Scala/Java Date.

Scala

  1. import scalajs.js
  2. implicit def convertFromJSDate(d: js.Date): java.util.Date = {
  3. new java.util.Date(d.getMilliseconds())
  4. }
  5. implicit def convertToJSDate(d: java.util.Date): js.Date = {
  6. new js.Date(d.getTime)
  7. }
  8. case class Person(name: String, joined: js.Date)
  9. val p = Person("James Bond", new java.util.Date)

When these implicit conversion functions are in lexical scope, you can use JS and Scala dates interchangeably. Outsidethe scope they are not visible and you must use correct types or explicitly convert between each other.

Implicit conversions for “monkey patching”

The monkey patching term became famous among Ruby developers and it has been adopted into JavaScript to describea way of extending existing classes with new methods. It has several pitfalls in dynamic languages and is generallynot a recommended practice. Especially dangerous is to patch JavaScript’s host objects like String or DOM.Node. Thistechnique is, however, commonly used to provide support for new JavaScript functionality missing from older JS engines.The practice is known as polyfilling or shimming.

In Scala providing extension methods via implicits is perfectly safe and even a recommended practice. The Scalastandard library does it all the time. For example did you notice the .r or .toInt functions that were used onstrings in the regex example? Both are extension methods coming from implicit classes.

Let’s use the convertToDate we defined before and add a toDate extension method to String by defining an implicitclass.

ES6

  1. String.prototype.toDate = function() {
  2. return convertToDate(this);
  3. }
  4. "2015-10-09".toDate(); // = {year:2015,month:10,day:9}

Scala

  1. implicit class StrToDate(val s: String) {
  2. def toDate = convertToDate(s)
  3. }
  4. "2015-10-09".toDate // = Date(2015,10,9)

Note that the JavaScript version modifies the global String class (dangerous!), whereas the Scala version onlyintroduces a conversion from String to a custom StrToDate class providing an additional method. Implicit classes aresafe because they are lexically scoped, meaning the StrToDate is not available in other parts of the program unlessexplicitly imported. The toDate method is not added to the String class in any way, instead the compiler generatesappropriate code to call it when required. Basically "2010-10-09".toDate is converted into newStrToDate("2010-10-09").toDate.

Scala IDEs are also smart enough to know what implicit extension methods are in scope and will show them to you nextto the other methods.

From ES6 to Scala: Advanced - 图1

Implicit extension methods are safe and easy to refactor. If you, say, rename or remove a method, the compiler willimmediately give errors in places where you use that method. IDEs provide great tools for automatically renaming allinstances when you make the change, keeping your code base operational. You can even do complex changes like add newmethod parameters or reorder them and the IDE can take care of the refactoring for you, safely and automatically, thanksto strict typing.

Finally we’ll make DOM’s NodeList behave like a regular Scala collection to make it easier to work with them. Or to bemore accurate, we are extending DOMList[T] which provides a type for the nodes. NodeList is actually just aDOMList[Node].

Scala

  1. implicit class NodeListSeq[T <: Node](nodes: DOMList[T]) extends IndexedSeq[T] {
  2. override def foreach[U](f: T => U): Unit = {
  3. for (i <- 0 until nodes.length) {
  4. f(nodes(i))
  5. }
  6. }
  7. override def length: Int = nodes.length
  8. override def apply(idx: Int): T = nodes(idx)
  9. }

Defining just those three functions, we now have access to all the usual collection functionality like map, filter,find, slice, foldLeft, etc. This makes working with NodeLists a lot easier and safer. The implicit class makesuse of Scala generics, providing implementation for all types that extend Node. Note that NodeListSeq is available as PimpedNodeList in the scala-js-dom library; just import org.scalajs.dom.ext._ to use it.

Scala

  1. // cast to correct element type
  2. val images = dom.document.querySelectorAll("img").asInstanceOf[NodeListOf[HTMLImageElement]]
  3. // get all image source URLs
  4. val urls = images.map(i => i.src)
  5. // filter images that have "class" attribute set
  6. val withClass = images.filter(i => i.className.nonEmpty)
  7. // set an event listener to 10 widest images
  8. images.sortBy(i => -i.width).take(10).foreach { i =>
  9. i.onclick = (e: MouseEvent) => println("Image clicked!")
  10. }

Futures

Writing asynchronous JavaScript code used to be painful due to the number of callbacks required to handle chainedasynchronous calls. This is affectionately known as callback hell. Then came the various Promise libraries thatalleviated this issue a lot, but were not fully compatible with each other. ES6 standardizes the Promiseinterface so that all implementations (ES6’s own included) can happily coexist.

In Scala a similar concept is the Future. On the JVM, futures can be used for both paralleland asynchronous processing, but under Scala.js only the latter is possible. Like a JavaScript Promise, a Future is aplaceholder object for a value that may not yet exist. Both Promise and Future can complete successfully, providinga value, or fail with an error/exception. Let’s look at a typical use case of fetching data from server using AJAX.

ES6

  1. // using jQuery
  2. $.ajax("http://api.openweathermap.org/" +
  3. "data/2.5/weather?q=Tampere").then(
  4. (data, textStatus, jqXHR) =>
  5. console.log(data)
  6. );

Scala

  1. import org.scalajs.dom
  2. import dom.ext.Ajax
  3. Ajax.get("http://api.openweathermap.org/" +
  4. "data/2.5/weather?q=Tampere").foreach {
  5. xhr =>
  6. println(xhr.responseText)
  7. }

The JavaScript code above is using jQuery to provide similar helper for making Ajax calls returning promises as isavailable in the Scala.js DOM library.

Here is a comparison between Scala’s Future and JavaScript’s Promise for the most commonly used methods.

FuturePromiseNotes
foreach(func)then(func)Executes func for its side-effects when the future completes.
map(func)then(func)The result of func is wrapped in a new future.
flatMap(func)then(func)func must return a future.
recover(func)catch(func)Handles an error. The result of func is wrapped in a new future.
recoverWith(func)catch(func)Handles an error. func must return a future.
filter(predicate)N/ACreates a new future by filtering the value of the current future with a predicate.
zip(that)N/AZips the values of this and that future, and creates a new future holding the tuple of their results.
Future.successful(value)Promise.resolve(value)Returns a successful future containing value
Future.failed(exception)Promise.reject(value)Returns a failed future containing exception
Future.sequence(iterable)Promise.all(iterable)Returns a future that completes when all of the futures in the iterable argument have been completed.
Future.firstCompletedOf(iterable)Promise.race(iterable)Returns a future that completes as soon as one of the futures in the iterable completes.

Note that Scala has different functions corresponding to JavaScript’s then, mainly map and flatMap.then is not type-safe, because it will flatten promises “all the way down”, even if that was not your intention.In contrast, map never flattens, and flatMap always flattens once, tracking the appropriate static result type.

foreach is a slight variation of map that does not return a new future.It is typically used instead of map to communicate the intent that the callbackis executed for its side-effects rather than its result value.

Futures from callbacks

Even though ES6 brought the standard promise API to browsers, all asynchronous functions still require the use ofcallbacks. To convert a callback into a Future in Scala you need to use a Promise. Wait, what? Yes, in addition toFuture, Scala also has a Promise class which actually implements the Future trait.

As an example, let’s convert the onload event of an img tag into a Future.

ES6

  1. function onLoadPromise(img) {
  2. if (img.complete) {
  3. return Promise.resolve(img.src);
  4. } else {
  5. const p = new Promise((success) => {
  6. img.onload = (e) => {
  7. success(img.src);
  8. };
  9. });
  10. return p;
  11. }
  12. }
  13. const img = document.querySelector("#mapimage");
  14. onLoadPromise(img).then(url =>
  15. console.log(`Image ${url} loaded`)
  16. );

Scala

  1. def onLoadFuture(img: HTMLImageElement) = {
  2. if (img.complete) {
  3. Future.successful(img.src)
  4. } else {
  5. val p = Promise[String]()
  6. img.onload = { (e: Event) =>
  7. p.success(img.src)
  8. }
  9. p.future
  10. }
  11. }
  12. val img = dom.document.querySelector("#mapimage")
  13. .asInstanceOf[HTMLImageElement]
  14. onLoadFuture(img).foreach { url =>
  15. println(s"Image $url loaded")
  16. }

Because the image might have already loaded when we create the promise, we must check for that separately and just return acompleted future in that case.

Next we’ll add an onloadF extension method to the HTMLImageElement class, to make it really easy touse the futurized version.

Scala

  1. implicit class HTMLImageElementOps(val img: HTMLImageElement) extends AnyVal {
  2. def onloadF = onLoadFuture(img)
  3. }
  4. val img = dom.document.querySelector("#mapimage").asInstanceOf[HTMLImageElement]
  5. img.onloadF.foreach { url =>
  6. println(s"Image $url loaded")
  7. }

While we are playing with DOM images, let’s create a future that completes once all the images on the page havefinished loading. Here we’ll take advantage of the NodeListSeq extension class to provide us with the map methodon the NodeList returned from querySelectorAll:org.scalajs.dom.raw.NodeList).

Scala

  1. val images = dom.document.querySelectorAll("img").asInstanceOf[NodeListOf[HTMLImageElement]]
  2. val loaders = images.map(i => i.onloadF)
  3. Future.sequence(loaders).foreach { urls =>
  4. println(s"All ${urls.size} images loaded!")
  5. }