Contracts with Traceur
Having written about contracts in software, I became aware that I already wrote a tool to support contracts for REST called raml-tester. The idea popped up to also do something for function/class level contracts.
Recently, I saw that for AngularJS 2.0, they will use an extended version of Javascript called AtScript. The way they do it, is to use Traceur, a compiler which transforms new javascript language elements back to current style javascript to run it in today’s browsers.
So I used Traceur to add Design by contract to javascript.
There’s not much documentation about Traceur in the web, this is pretty much all I found. But the code is clean and nicely structured, it’s pretty straight forward to add new features to it. Basically, there are two steps in order to add a new language feature:
- Extend the parser to check if the feature is used correctly and to produce a syntax tree for the feature. This tree may contain elements that are not valid javascript code.
- Add a transformer that takes the syntax tree and transforms it into a valid javascript syntax tree.
Although the first steps are surprisingly easy, one can easily get trapped in the subtleties of the implementation. At one point, I was even considering if I could use the famous Y-combinator (Javascript code).
But in the end, everything was solved without too much magic. Especially writing the transformers reminded me heavily on writing macros (in Scala). This is all natural if one thinks of macros as a kind of compiler plugin.
Creating syntax trees is a little bit heavy. It looks something like this:
let thisInvariants = createMemberExpression(
createThisExpression(), createIdentifierToken('__invariants')),
invariantsIsFunc = createBinaryExpression(
createUnaryExpression(createOperatorToken(TYPEOF), thisInvariants),
createOperatorToken(EQUAL_EQUAL_EQUAL),
createStringLiteral('function')),
shouldCallInvariants = createBinaryExpression(
createThisExpression(), createOperatorToken(AND), invariantsIsFunc),
checkInvariants = createIfStatement(
shouldCallInvariants, createCallStatement(thisInvariants));
But then I discovered an easier way. Because Traceur itself is also written in ES6, one can use Template Strings to express the same thing much clearer:
let name = '__invariants';
parseStatement `
if (this && typeof this.${name} === 'function'){
this.${name}();
}`;
Wow! This is pure coolness! The string between backticks is transformed into a syntax tree. …And exactly the same thing is possible in scala macros. I don’t know who copied from whom, but who cares.
After two days of playing with it, I’m happy with the result. Sure it’s not production ready, but it’s enough to have fun.
require
andensure
are used to express preconditions and postconditions of a function. They are checked at runtime.- Inside
ensure
$old
contains the variable values before the execution of the function.$result
contains the return value of the function.
invariant
expresses conditions of an object that are true after every method invocation. They are also checked at runtime.pure
marks a function as pure, meaning that it accesses neitherthis
nor variables from outside its scope (global values). This is checked at compile time.pure(this)
works likepure
but allows access tothis
.
The source code is on github, and there’s a playground here.