valve's

corner

Existential Operator in CoffeeScript

Existential operator ? can be used in three useful ways in CoffeeScript.

Checking the existence of a variable

In JavaScript there is no built-in way of checking the existence of a variable. You can try testing the existence with if(variable){...} but it won’t work in these cases:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(0){
  console.log('this will not print');
}

if(""){
  console.log('this will not print');
}

if(false){
  console.log('this will not print');
}

// abc was never declared
if(abc){
  //ReferenceError: abc is not defined
}

The correct way to test if variable was both declared and initialized:

1
2
3
if(typeof variable !== 'undefined' && variable !== null){
  console.log('variable was declared and initialized with a value');
}

You may be tempted to use direct comparison with undefined:

1
if(variable !== undefined ...

But this is asking for trouble because in EcmaScript 3 (all older browsers, such as IE 6-8) undefined can be overwritten:

1
2
3
4
5
window.undefined = 'pancakes';

if('pancakes' === undefined){
  console.log('This will print, if you are unfortunate enough to use IE 6-8');
}

All ES5 compatible browsers have undefined immutable, i.e. it can’t be changed, but it’s better to play it safe.

CoffeeScript has a syntactic shortcut for testing existence:

CoffeeScript
1
2
if variable?
  console.log('variable is both declared and initialized with a non-null value');

This code will be transpiled to:

1
2
3
if (typeof variable !== "undefined" && variable !== null) {
  console.log('If variable was both declared and initialized with a non-null value');
}

Conditional assignment

What if you want to initialize a variable only if it has not been already initialized? In JavaScript you’d usually do something like:

1
2
3
4
5
6
function getUserLocale(){
  if(this.locale == null){
    this.locale = DB.getLocaleByUser(User.current);
  }
  return this.locale;
}

This technique caches the result of an expensive computation or database query in a variable. In above example, all subsequent getUserLocale function calls will not query the database. The important part is comparing with null using ==, rather than ===, because == will evaluate to true if variable is either undefined or null.

Such cache on first call pattern is widely used in many programming languages, but CoffeeScript has a special syntax for it:

CoffeeScript
1
2
getUserLocale = ->
  @locale ?= DB.getLocaleByUser(User.current)

?= is the operator that performs conditional assignment.

This will be transpiled to roughly the same JS as above, using ternary operator:

1
2
3
getUserLocale = function() {
  return this.locale != null ? this.locale : this.locale = DB.getLocaleByUser(User.current);
};

What if you try to conditionally assign an undeclared variable? If you try this:

CoffeeScript
1
2
# abc is not declared 
abc ?= 99

This will result in a compile-time error: the variable "abc" can't be assigned with ?= because it has not been declared before. This compile-time checking is very helpful, because it prevents a ReferenceError at run time.

If you know Ruby, ||= is the same thing there.

Safe property / function chaining

Chaining function calls is a great way to write terse yet fluent code. A good example is working with jQuery:

1
$('#header').css('color', '#fadfad').show('slow').off();

This is made possible because these jQuery functions return a reference to this. But what if one of the function returns null or undefined?

1
var zip = User.current.address.zip

If current user’s address is null or undefined, the .zip property call will result in TypeError.

A simple but ugly solution would be to use a lot of if checks:

1
2
3
4
5
var zip = null;
if(User.current != null && User.current.address != null){
  // only now this is safe
  zip = User.current.address.zip;
}

But this can quickly get out of hand with deep nesting.

CoffeeScript has a safe way of accessing long property chains using ?. variant of existential operator:

CoffeeScript
1
zip = User.current?.address?.zip

This will either soak up the null or undefined references and safely return undefined or return the final property value. In our case the generated code looks like this:

1
2
3
var zip, _ref;

zip = (_ref = User.current) != null ? _ref.address.zip : void 0;

What is this weird void 0 thing? This is to fight the pre AS5 undefined mutability I referred to earlier. JavaScript defines void as a unary operator that returns undefined for any argument. In other words, CoffeeScript compiler uses a set of nested ternary operators to safely return either last property value or undefined with void 0.

Calling a function safely works similarly:

CoffeeScript
1
noSuchFunction?()

This transpiles to:

1
2
3
if (typeof noSuchFunction === 'function') {
  noSuchFunction();
}

Key thing to take away here is that CoffeeScript first tests that callable function is defined and is a function. It does this using typeof bla === 'function'. The function is called only if it is defined.

Safe function invocation can be chained as well with other function or property calls:

example from coffeescript.org
1
lottery.drawWinner?().address?.zip

This transpiles to:

1
2
3
4
5
6
var zip, _ref;
zip = typeof lottery.drawWinner === "function"
  ?
    (_ref = lottery.drawWinner().address) != null ? _ref.zipcode : void 0
  :
    void 0;

Drawing analogy with Ruby-on-Rails ActiveSupport, the safe chaining can be seen as try method.

Conclusion:

CoffeeScript existential operator is a useful tool to cut down the verbosity of JavaScript when dealing with existence and null checks. It also can be used to shield inexperienced JavaScript developers from JavaScript bad parts.

Comments