From ES6 to Scala: Advanced

Scala is a feature rich language that is easy to learn but takes time to master. Depending on your programming background, typically you start by writing Scala as you would’ve written the language you know best (JavaScript, Java or C# for example) and gradually learn more and more idiomatic Scala paradigms to use. In this section we cover some of the more 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 switch statement. However, it can be used for much more, for example checking the type of input.

ES6
function printType(o) {
  switch (typeof o) {
    case "string":
      console.log(`It's a string: ${o}`);
      break;
    case "number":
      console.log(`It's a number: ${o}`);
      break;
    case "boolean":
      console.log(`It's a boolean: ${o}`);
      break;
    default:
      console.log(`It's something else`);
  }
}
Scala
def printType(o: Any): Unit = {
  o match {
    case s: String =>
      println(s"It's a string: $s")
    case i: Int =>
      println(s"It's an int: $i")
    case b: Boolean =>
      println(s"It's a boolean: $b")
    case _ =>
      println("It's something else")
}

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

ES6
function parse(str, magicKey) {
  let res = [];
  for(let c of str) {
    if (c === magicKey)
      res.push("magic");
    else if (c.match(/\d/))
      res.push("digit");
    else if (c.match(/\w/))
      res.push("letter");
    else if (c.match(/\s/))
      res.push(" ");
    else
      res.push("char");
  }
  return res;
}
const r = parse("JB/007", '/');
// [letter, letter, magic, digit, digit, digit]
Scala
def parse(str: String, magicKey: Char): String = {
  str.map {
    case c if c == magicKey =>
      "magic"
    case c if c.isDigit =>
      "digit"
    case c if c.isLetter =>
      "letter"
    case c if c.isWhitespace =>
      " "
    case c =>
      "char"
  }
}
val r = parse("JB/007", '/')
// 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 extracting values inside that structure. ES6 also supports destructuring (yay!) in assignments and function parameters, but not in matching.

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

const seq2 = [a, b].concat(c); // [1, 2, 4, 5]
Scala
case class Person(first: String, last: String, age: Int)
val person = Person("James", "Bond", 42)
val Person(first, last, years) = person
// first = "James", last = "Bond", years = 42
val seq = Seq(1, 2, 3, 4, 5)
val Seq(a, b, _, c @ _*) = seq
// a = 1, b = 2, c = Seq(4, 5)

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 skip values in destructuring.

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

ES6
function ageSum(persons, family) {
  return persons.filter(p => p.last === family)
    .reduce((a, p) => a + p.age, 0);
}
const persons = [
  {first: "James", last: "Bond", age: 42},
  {first: "Hillary", last: "Bond", age: 35},
  {first: "James", last: "Smith", age: 55}
];

ageSum(persons, "Bond") == 77;
Scala
def ageSum(persons: Seq[Person],
    family: String): Int = {
  persons.collect {
    case Person(_, last, age) if last == family =>
      age
  }.sum
}
val persons = Seq(
  Person("James", "Bond", 42),
  Person("Hillary", "Bond", 35),
  Person("James", "Smith", 55)
)

ageSum(persons, "Bond") == 77

We could’ve implemented the Scala function using a filter and foldLeft, but it is more understandable using collect and pattern matching. It would be read as “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 different formats.

ES6
function convertToDate(d) {
  const YMD = /(\d{4})-(\d{1,2})-(\d{1,2})/
  const MDY = /(\d{1,2})\/(\d{1,2})\/(\d{4})/
  const DMY = /(\d{1,2})\.(\d{1,2})\.(\d{4})/

  const [, year, month, day] = YMD.exec(d) || [];
  if (year !== undefined) {
    return {
      year: parseInt(year),
      month: parseInt(month),
      day: parseInt(day)
    };
  } else {
    const [, month, day, year] = MDY.exec(d) || [];
    if (year !== undefined) {
      return {
        year: parseInt(year),
        month: parseInt(month),
        day: parseInt(day)
      };
    } else {
      const [, day, month, year] = DMY.exec(d) || [];
      if (year !== undefined) {
        return {
          year: parseInt(year),
          month: parseInt(month),
          day: parseInt(day)
        };
      }
    }
  }
  throw new Error("Invalid date!");
}
convertToDate("2015-10-9"); //{year:2015,month:10,day:9}
convertToDate("10/9/2015"); //{year:2015,month:10,day:9}
convertToDate("9.10.2015"); //{year:2015,month:10,day:9}
convertToDate("10 Nov 2015"); // exception
Scala
case class Date(year: Int, month: Int, day: Int)

def convertToDate(d: String): Date = {
  val YMD = """(\d{4})-(\d{1,2})-(\d{1,2})""".r
  val MDY = """(\d{1,2})/(\d{1,2})/(\d{4})""".r
  val DMY = """(\d{1,2})\.(\d{1,2})\.(\d{4})""".r
  d match {
    case YMD(year, month, day) =>
      Date(year.toInt, month.toInt, day.toInt)
    case MDY(month, day, year) =>
      Date(year.toInt, month.toInt, day.toInt)
    case DMY(day, month, year) =>
      Date(year.toInt, month.toInt, day.toInt)
    case _ =>
      throw new Exception("Invalid date!")
  }
}

convertToDate("2015-10-9") // = Date(2015,10,9)
convertToDate("10/9/2015") // = Date(2015,10,9)
convertToDate("9.10.2015") // = Date(2015,10,9)
convertToDate("10 Nov 2015") // exception

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

Implicits

Being type safe is great in Scala, but sometimes the type system can be a bit prohibitive when you want to do something else, 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 can automatically provide it. For example we can provide a function to automatically convert a JavaScript Date into a Scala/Java Date.

Scala
import scalajs.js

implicit def convertFromJSDate(d: js.Date): java.util.Date = {
  new java.util.Date(d.getMilliseconds())
}

implicit def convertToJSDate(d: java.util.Date): js.Date = {
  new js.Date(d.getTime)
}

case class Person(name: String, joined: js.Date)

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. Outside the scope they are not visible and you must use correct types or provide conversion yourself.

Implicit conversions for “monkey patching”

Monkey patching -term became famous among Ruby developers and it has been adopted into JavaScript to describe a way of extending existing classes with new methods. It has several pitfalls in dynamic languages and is generally not a recommended practice. Especially dangerous is to patch JavaScript’s host objects like String or DOM.Node. This technique 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. Scala standard library does it all the time. For example did you notice the .r or .toInt functions that were used on strings 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 implicit class.

ES6
String.prototype.toDate = function() {
  return convertToDate(this);
}
"2015-10-09".toDate; // = {year:2015,month:10,day:9}
Scala
implicit class StrToDate(val s: String) {
  def toDate = convertToDate(s)
}
"2015-10-09".toDate // = Date(2015,10,9)

Note that the JavaScript version modifies the global String class (dangerous!), whereas the Scala version only introduces a conversion from String to a custom StrToDate class providing an additional method. Implicit classes are safe because they are lexically scoped, meaning the StrToDate is not available in other parts of the program unless explicitly imported. The toDate method is not added to the String class in any way, instead the compiler generates appropriate code to call it when required. Basically "2010-10-09".toDate is converted into new StrToDate("2010-10-09").toDate which is then inlined/optimized (due to the use of Value Class) to convertToDate("2010-10-09") at the call site.

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

Implicit extension methods are safe and easy to refactor. If you, say, rename or remove a method, the compiler will immediately give errors in places where you use that method. IDEs provide great tools for automatically renaming all instances when you make the change, keeping your code base operational. You can even do complex changes like add new method parameters or reorder them and the IDE can take care of the refactoring for you, safely and automatically, thanks to 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 be more accurate, we are extending DOMList[T] which provides a type for the nodes. NodeList is actually just a DOMList[Node].

Scala
implicit class NodeListSeq[T <: Node](nodes: DOMList[T]) extends IndexedSeq[T] {
  override def foreach[U](f: T => U): Unit = {
    for (i <- 0 until nodes.length) {
      f(nodes(i))
    }
  }

  override def length: Int = nodes.length

  override def apply(idx: Int): T = nodes(idx)
}

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 makes use of Scala generics, providing implementation for all types that extend Node.

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

Futures

Writing asynchronous JavaScript code used to be painful due to the number of callbacks required to handle chained asynchronous calls. This is affectionately known as callback hell. Then came the various Promise libraries that alleviated this issue a lot, but were not fully compatible with each other. ES6 standardizes the Promise interface 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 parallel and asynchronous processing, but under Scala.js only the latter is possible. Like the Promise a Future is a placeholder object for a value that may not yet exist. Both Promise and Future can complete successfully, providing a value, or fail with an error/exception. Let’s look at a typical use case of fetching data from server using Ajax.

ES6
// using jQuery

$.ajax("http://api.openweathermap.org/" +
    "data/2.5/weather?q=Tampere").then(
   (data, textStatus, jqXHR) =>
      console.log(data)
);
Scala
import org.scalajs.dom
import dom.ext.Ajax

Ajax.get("http://api.openweathermap.org/" +
    "data/2.5/weather?q=Tampere").foreach {
  xhr =>
    println(xhr.responseText)
}

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

Comparison between Scala Future and JavaScript Promise methods.

FuturePromiseNotes
foreach(func)then(func)Does not return a new promise.
map(func)then(func)Return value of func is wrapped in a new promise.
flatMap(func)then(func)func must return a promise.
recover(func)catch(func)Handle error. Return value of func is wrapped in a new promise.
recoverWith(func)catch(func)Handle error. func must return a promise.
onComplete(func)then(func, err)Callback for handling both success and failure cases.
onSuccess(func)then(func)Callback for handling only success cases.
onFailure(func)catch(func)Callback for handling only failure cases.
transform(func, err)then(func, err)Combines map and recover into a single function.
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 promises in the iterable argument have completes.
Future.firstCompletedOf(iterable)Promise.race(iterable)Returns a future that completes as soon as one of the promises in the iterable completes.

Futures from callbacks

Even though ES6 brought the standard promise API to browsers, all asynchronous functions still require the use of callbacks. To convert a callback into a Future in Scala you need to use a Promise. Wait, what? Yes, in addition to Future, 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
function onLoadPromise(img) {
  if (img.complete) {
    return Promise.resolve(img.src);
  } else {
    const p = new Promise((success) => {
      img.onload = (e) => {
        success(img.src);
      };
    });
    return p;
  }
}

const img = document.querySelector("#mapimage");
onLoadPromise(img).then(url =>
  console.log(`Image ${url} loaded`)
);
Scala
def onLoadFuture(img: HTMLImageElement) = {
  if (img.complete) {
    Future.successful(img.src)
  } else {
    val p = Promise[String]()
    img.onload = { (e: Event) =>
      p.success(img.src)
    }
    p.future
  }
}

val img = dom.document.querySelector("#mapimage")
  .asInstanceOf[HTMLImageElement]
onLoadFuture(img).foreach { url =>
  println(s"Image $url loaded")
}

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

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

Scala
implicit class HTMLImageElementOps(val img: HTMLImageElement) extends AnyVal {
  def onloadF = onLoadFuture(img)
}

val img = dom.document.querySelector("#mapimage").asInstanceOf[HTMLImageElement]
img.onloadF.foreach { url =>
  println(s"Image $url loaded")
}

While we are playing with DOM images, let’s create a future that completes once all the images on the page have completed loading. Here we’ll take advantage of the NodeListSeq extension class to provide us with the map method on the NodeList returned from querySelectorAll.

Scala
val images = dom.document.querySelectorAll("img").asInstanceOf[NodeListOf[HTMLImageElement]]
val loaders = images.map(i => i.onloadF)

Future.sequence(loaders).foreach { urls =>
  println(s"All ${urls.size} images loaded!")
}