JavaScript for Scala Developers

January 28, 2017

Disclaimer : this post is not about Scala.js, but about the JavaScript language (+ Typescript).

As you may know, the last published JavaScript standard is ECMAScript 2016 (or ECMAScript 7, or ES7). Since ES6 (the previous standard, published in 2015), JS has a lot new syntactic possibilities. Many of this features will make Scala developers feel at home, at least a lot more than previous JS versions. Important note : it is possible to target browsers that don’t fully support ES6 or ES7 using the Babel compiler.

Basic examples

Define a variable

Scala :

var x = 1

JS :

let x = 1

Note : let is aimed at replacing JS var, as it defines block scoped variables.

Define a constant (a value)

Scala :

val x = 1

JS :

const x = 1

Both of this values are not reassignable. In JS you will have an “invalid assignment” error if you try to redefine a const.

Note : like let, const is block scoped.

Functions


// old school JS functions
const f = function(x) {
  return x +1
};

// Or arrow functions
const f = (x => x +1);

The second syntax is closer to Scala, and is nice when you need to pass a function argument to another function.

Classes

Since ES6, it is possible to define classes in JavaScript.

class Point {
       constructor(x, y) {
           this.x = x;
           this.y = y;
       }
       toString() {
           return '(' + this.x + ', ' + this.y + ')';
       }
   }

const p = new Point(1,2);

Default parameters

function multiply(a, b = 1) {
  return a * b;
}

multiply(5, 2); // 10
multiply(5, 1); // 5
multiply(5);    // 5

Generators

Generators are functions that can produce a sequence of values :

function* idMaker(index = 0, max=10){
    yield index;
    if(index < max)
      yield* idMaker(index+1);
}
let gen = idMaker();

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

Generator functions are prefixed with a *. yield may remind you of Scala for comprehension. This keyword is used to produce an element in the generator. yield* is used to append elements from another generator.

Destructuring


//define x and y coordinates from a point :
let { x, y } = new Point(1,2);

Note : unlike Scala, JS asks you to use the same parameter names in the left expression as in your object.

Spread operator

This operator can be used to structure and destructure arrays.

let [...values] = idMaker();
console.log(values); //0 to 10

let [head, ...tail] = values;
console.log(head); //0
console.log(tail); //1 to 10

Rest operator

let f = function(a, b, ...rest) {
  // ...
}

This operator works like Scala varargs. parameters is retrieved as an Array object.

Usage :

f("a","b", 1 ,2 ,3) //1, 2, 3 will be in the rest array

Modules and imports

In lib.js :

export function f(x) {
    return x+1;
};

// other functions ...

In your JS file :

import { f, g } from 'lib';

const a = f(1);

Promises

JS Promises are close to Scala Futures :

let result = Promise.resolve("Success")
.then((value) =>"result :" + value);
//Promise { <state>: "fulfilled", <value>: "result :Success" }

//combine 2 Promises :
let result2 = Promise.resolve("Success")
 .then((value) => Promise.resolve("result " + value + " Success 2"));
//Promise { <state>: "fulfilled", <value>: "result Success Success 2" }

//print result :
Promise.resolve("Success")
.then((value) => Promise.resolve("result " + value + " Success 3"))
.then((value) => console.log(value));

Where are my types ?

One of the biggest difference with Scala is the absence of types. TypeScript is a typed superset of JavaScript, that can be compiled and transformed (transpiled) to JS. It is the language that has been used to write the Angular framework (since Angular 2), and it is the recommended language to develop Angular applications as well.

Let’s see how it looks :

let result: Promise<string> = Promise.resolve("Success")
.then((value) =>"result :" + value);

let result2: Promise<string>  = Promise.resolve("Success")
.then((value) => Promise.resolve("result " + value + " Success 2"));

You can see that I’ve added some type definitions to my result variable. The type notation is similar to Scala type notation. let result: Promise<string> tells the compiler that this variable must be a Promise of string. If it’s not the case, it will produce a compilation error.

Note: You can notice that there is an automatic flatten of JS Promises, as my result2 is a Promise<string> and not a Promise<Promise<string>>. This is not specific to the TypeScript language (pure JS promises are also flatten).

Type aliases with TypeScript

TypeScript type aliases look like Scala aliases :

type Name = string;
type NameResolver = () => string;

Immutable data

We have const to define values, but it is possible to get further with Immutable.js collections and records.

React, Redux and Angular are frameworks that rely a lot on immutability.

ES proposal : rest/spread properties

This proposal would be very convenient to copy objects.

Object copy in Scala (using case classes) :

case class Person(name: String, age: Int)

val bob = Person("bob", 30)
val john = bob.copy(name="john")

JS :

const bob = {name: "bob", age: 30};
const obj1 = {...bob, name: "john", address: "25 5th street NYC"}; // copy bob with updated name and address added

And it can already be used with Babel!

More advanced examples

Extensions methods

With JS you can add methods on any class via its prototype (the concept behind JS object oriented programming):

let add = (x, y) => x.sum(y);

//extension
Number.prototype.sum = function (x) { return this + x };

console.log(add(1, 2)); //3

With TypeScript you can declare that a function needs an object with a specific method (or several methods), using an interface :

interface CanSum {
    sum(x: CanSum): Number;
}

let add = (x: CanSum, y: CanSum) => x.sum(y);

//extension
Number.prototype.sum = function (x) { return this + x };

console.log(add(1, 2)); //3

Note : interfaces are implicitly implemented if an object defines all methods of the interface (it is called structural subtyping).

With extension methods and structural subtyping, you can emulate Scala implicit classes and be close to type classes, but without the recursive power of implicit resolution of type class instances. For example, if I need a class P to be serializable to a format X, I can add a serializeToX method on it instead of defining an implicit XSerializer[P], and P could be seen as implementing a CanBeSerialized interface. But, if P contains other types that need to be serialized, serializers won’t be discovered implicitly and recursively. You will have to define methods on each type.

Other functional goodness

If you miss functional abstractions like Option, Either and even Free monad, take a look at Monet.js, an amazing functional JS library. As types are really important for this kind of library (Maybe/Option monad for instance is not very useful without types), you can use Monet with its TypeScript bindings.

Discussion, links, and tweets

comments powered by Disqus