Introduction

Clean code is satisfying. Consistent visual structure makes it even better. returnif is a whimsical keyword that helps make code beautiful.

returnif allows one to return from a function conditionally based on 1) the truthiness of the value to be returned or 2) a precondition.

Case 1: the truthiness of the value to be returned

If the value of the expression to be returned is truthy, return it.

Consider the common pattern of early function termination based on argument validation.

function f(arg) {
    var err = validate(arg);
    if (err) {
        return err;
    }

    doSomething(arg);
    ...
}

With a conditional return, we can do the same thing in a single line.

function f(arg) {
    returnif validate(arg);

    doSomething(arg);
    ...
}

Case 2: a precondition

If the value of the precondition expression is truthy, return the value of the consequent expression.

Consider a similar pattern, but where we wish to return some other value.

function fibonacci(num, results = {}) {
    returnif results[num]; // case 1!

    // replace this with `returnif`
    if (num <= 1) {
        return 1;
    }

    return results[num] = (
        fibonacci(num - 1, results) +
        fibonacci(num - 2, results)
    );
}

With a conditional return, we can do the same thing in a single line.

function fibonacci(num, results = {}) {
    returnif results[num];
    returnif (num <= 1) : 1;
    return results[num] = (
        fibonacci(num - 1, results) +
        fibonacci(num - 2, results)
    );
}

returnif starts looking like a pattern match structure (like in OCaml), or _.cond from the JS lodash library.

That’s it! A new building block. Infinite possibilities. Syntactic sugar for return and if. Use wisely.

Build

Here’s a quick, proof-of-concept hack to transpile returnif in JavaScript via the Babel compiler. The forgivingness of JavaScript and the prevalence of the Babel compiler seemed like the quickest way to bring returnif to life.

Adding a new keyword or language concept requires modification to the parsing of the abstract syntax tree (AST) so, returnif cannot be added through a Babel plugin alone.

I’ve only (kinda) done case 1 – consider case 2 an exercise for the reader.

Step 1 – Parse

Add returnif to the Babel AST parser.

I forked Babel and added a few lines to interpret returnif similar to return. Their declarations and representations in the AST are the same for case 1.

interface ReturnStatement <: Statement {
  type: "ReturnStatement";
  argument: Expression | null;
}

interface ReturnIfStatement <: Statement {
  type: "ReturnIfStatement";
  argument: Expression | null;
}

adamlouis/babel | Pull request for steps 1 & 2

Step 2 – Generate

Generate vanilla js from the ReturnIfStatement introduced in step 1.

Since returnif is syntactic sugar for return and if, generation builds a template like:

var a = <ReturnIf.argument>;
if (a) {
    return a;
}

If we create a temporary var in the template:

  1. its name must not collide with others in our program
  2. transpiling should remain deterministic

Using the hash of the AST node as a variable name suffix (probably) achieves these 2 properties. This seems both clever and dirty, but it’s what I went with.

var ReturnIf_<hash(node)> = <ReturnIf.argument>;
if (ReturnIf_<hash(node)>) {
    return ReturnIf_<hash(node)>;
}

Is there another way to do this? An approach where we don’t need a var name? Prior art?

adamlouis/babel | Pull request for steps 1 & 2

Step 3 – Use

Use the new returnif keyword.

adamlouis/returnif includes adamlouis/babel as a git submodule and uses the new keyword. The adamlouis/retunif/index.js file:

  1. reads all files in js-samples dir as strings
  2. runs generate(parse(CODE))
  3. writes the AST and the transpiled JS to js-gen dir
  4. executes the generated js in the js-gen dir

Before:

function fibonacci(num, results = {}) {
    returnif results[num];

    // TODO -> returnif (num <= 1) : 1;
    if (num <= 1) {
        return 1;
    }

    return results[num] = (
        fibonacci(num - 1, results) +
        fibonacci(num - 2, results)
    );
}

After:

function fibonacci(num, results = {}) {
    var returnIf_2978980376 = (results[num]);
    if (returnIf_2978980376) {
        return returnIf_2978980376;
    }

    // TODO -> returnif (num <= 1) : 1;
    if (num <= 1) {
        return 1;
    }

    return results[num] = (
        fibonacci(num - 1, results) +
        fibonacci(num - 2, results)
    );
}

adamlouis/returnif | Repository for step 3

Thanks!

Thanks for reading my first website article! Send me a note at MYEMAIL, or on adamlouis/babel or adamlouis/returnif with and thoughts and feedback.