Semantic Inference for Succinctness

While working on JavaScript++, it occurred to me the programs were becoming verbose a la Java. For example, to define a pure function, we might write:

pure function foo(int bar, int baz) {
    // ...
}

While JavaScript++ will infer whether or not a function is pure, I could not find it in my heart to do away with the “pure” keyword. Why? Because, in large-scale applications, it is important to be able to apply explicit restrictions. If it is desired for a function to be pure, we do not want a team member accidentally introducing side effects later on.

To understand semantic inference, we first have to look at the basic for loop in JavaScript:

for (var i = 0; i < 100; i++);

This is the same for loop syntax inherited from the C language developed around 1970. We've made many advances in programming language theory in the 40+ years since C was first released.

Semantic inference evolved from the observation that the vast majority of for loops take one of the following forms:

for (var i = 0; i < 100; i++);
for (var i = 0; i <= 100; i++);
for (var i = 100; i > 0; i--);
for (var i = 100; i >= 0; i--);

Semantic inference can reduce for loops to simply:

loop i < 100: ...

The compiler analyses the loop statement, and infers the following:

  1. The language is block scoped. Therefore, a loop creates a new block.
  2. If "i" is not declared in any of the containing blocks, it will be declared and be scoped to the loop body.
  3. If "i" is declared in any of the containing blocks, we can imagine this as a for loop with an empty initialiser.
  4. We are using the < (less than) operator. Therefore, the loop should increment.

If this is blowing your mind, here are a few visualizations:

loop i < 100: ...;  // for (var i = 0; i < 100; i++);
loop i <= 100: ...; // for (var i = 0; i <= 100; i++);
loop i > 0: ...;    // for (var i = 100; i > 0; i--);
loop i >= 0: ...;   // for (var i = 100; i >= 0; i--);

You can still emulate a full for loop by adding initialization statements which precede the loop, and you can add increment operations within the loop body. However, these cases are rarer so it's unnecessary for us to have access to the full C syntax.

Micro-optimization fanatics may cry that they can no longer cache the length when iterating over an array. Not true: the compiler will automatically perform this optimization for you. This is an example of how typed optimizations can still improve performance in a dynamically typed environment.

However, semantic inference goes even further!

If semantic inference is used, there needs to only be ONE loop construct: loop. So how do we get a while loop? If you are not using any of the "alligator" comparison signs (hey, that was the analogy they used for elementary mathematics!) with a lone identifier on either side then we can infer that you want a while loop.

loop match = regex(): ...; // while (match = regex()) { ... }

Finally, semantic inference can also be safer. If you're incrementing or decrementing a for loop in the wrong direction, the result is an infinite loop. With semantic inference, the loop "direction" is determined for you.

In closing, semantic inference allows us to reduce verbosity and cognitive load.