git @ Cat's Eye Technologies Jaft / multi-returns
multi-returns

Tree @multi-returns (Download .tar.gz)

Jaft

Version 0.2 - subject to change without notice

Jaft is a small, well-behaved programming language which we can think of as a total and purely functional subset of JavaScript. (The reality is perhaps more complicated, but not in a bad way. For more details, see the Language Overview below.)

Jaft's design gives it some desirable properties:

  • Easier to analyze than imperative, object-oriented languages like JavaScript.
  • Suited to writing reactive functions like reducers, which should be total, pure functions in any case. Writing in JavaScript does not make this property easy to confirm.
  • Suited to writing event handlers, which should be total and highly restricted in their effects in any case. Writing in JavaScript does not make this property easy to confirm.
  • Very easy to implement in JavaScript, and straightforward to implement in other environments, making platform-independence of reduction logic and event handling code more feasible.
  • Jaft in concert with concoctor (Footnote 1) provides good support for gradually migrating existing JavaScript code to more tractable Jaft code.

Quick Start

The reference implementation of Jaft is written in JavaScript, and a concoctor interface has been built for it. In an environment that supports ES6 modules, using Jaft should be as easy as copying the source files to the desired location and importing

import { concoctJaft } from "./jaftConcoctor.js";

One can then call concoctJaft to create JavaScript functions from embedded snippets of Jaft source code, which can then be called.

const square = concoctJaft({}, '(a) => a * a');
console.log(square(5));

See the concoctor documentation for more information on how the concoctor system works.

Motivation / Case Study

Cosmos Boulders is a toy arcade-style video game written in JavaScript using an HTML5 canvas, built to show that the game logic could be written in purely functional JavaScript using immutable data structures and reducers.

Given that the implementation language is JavaScript, a complex modern production programming language, the confidence that the game logic was actually written in a purely functional style was not high.

The game logic was rewritten in Jaft, raising the confidence level. This will also allow the codebase to be refactored to use different techniques in functional programming.

Language Overview

This section serves as a motivational, high-level description of the Jaft language. For a fuller description, including many examples, see doc/Definition-of-Jaft.md.

When we say we can think of Jaft as a total and purely functional subset of JavaScript, we mean something which is, in full detail, more subtle. Still, the simplified version is a good way to think about it, because the ultimate goal is to prevent writing code that is non-total or non-pure by accident.

Let's start with syntax, because where that deviates from JavaScript is much easier to explain.

Syntax

Jaft's syntax is nearly a subset of JavaScript's syntax, but not quite, as let and const blocks are actually expression (and can e.g. appear inside parenthesizes subexpressions).

Jaft supports the following syntactic structures, which closely follow JavaScript:

  • function calls.
  • most JavaScript binary operators: +, *, etc.
  • ternary bool ? trueExpr : falseExpr syntax.
  • scoped let ident = expr1, ident2 = expr2; expr syntax. Note, unlike JavaScript syntax, this is an expression and can e.g. occur inside parentheses.
  • const ident = expr1, ident2 = expr2; expr syntax. Note that because all bindings are immutable, this is functionally identical to let, but if an identifier is bound with const then it cannot be shadowed (a parse error is thrown).

There is no unary minus, and currently no unary not, and (for more strictness) no == or != operator.

Now we move onto how the Jaft's semantics deviate from JavaScript, which is more subtle and requires more explanation.

Effects

Jaft code itself is purely functional: all data is immutable; it has no effects (like I/O or randomness or exceptions); execution is idempotent.

However, Jaft code may call externally supplied ("extern") functions provided to it. These functions may have computational effects, and these effects may be considered desirable, so we do not insist extern functions are on the same level of "purity" as Jaft.

Jaft functions which call extern functions with effects will naturally inherit those effects.

In the applications that Jaft is primaril designed for (reducers and event handlers), non-termination and mutation of accessible data are always considered undesirable effects. It is also useful to have tight control over other effects such as randomness. So it is useful to have a system which can analyze when functions have these effects and warn or prevent their construction. But such a system is outside the scope of Jaft itself. (See concoctor, however, for one such system.)

Non-termination

As suggested above, in Jaft non-termination is thought of an effect (TODO: link to that McBride(?) paper), and is always considered undesirable.

Jaft code itself is designed to prevent expressing non-terminating computations: recursion is not allowed. This prevention is mostly syntactic. However, when a Jaft function is passed other functions as arguments, the result may be non-terminating, even if the other functions are not extern functions. If termination cannot be determined statically, runtime checks must be made to prevent function calls from recursing.

Data and data types

All data in Jaft is considered immutable. (Recall that mutation of accessible data is always considered undesirable in Jaft.)

The data types in Jaft mirror those of JavaScript: in particular, boolean, number and string.

Data of other types may also occur; for example, externally supplied functions may return arbitary objects; but these data are effectively opaque, as the operations on them that can be expressed in Jaft are very limited.

TODO: We should define libraries which provide some common data immutable data structures (records, maps, etc.), which could be backed by the Immutable.js library in JavaScript. The interface would be part of Jaft's "standard library" in some sense. Actually, it should be part of concoctor, since as "extern" JavaScript functions, it would be available to any language using concoctor.

Function objects are supported as well. There are some rules about when functions can and cannot be called (see the section on Non-termination above.)

HISTORY

Version 0.2

  • Uses version 0.2 of concotor, in particular the new, simpler and much less naff interface that is exposed to compilers.
  • Support !== operator.
  • Officialy do not support != and == operators.
  • concoctor-level unit tests were moved to concoctor repo; jaftConcoctor-specific tests moved to own test suite.
  • Improves jafti to be more useful for the Falderl tests; it can interpret either an expression, or a function definition, in the latter case applying the function to a given value.

Version 0.1

  • Initial release.

Footnote 1

Some day there should be a better separation between Jaft and concoctor. Both currently reside in this repo. The reference implementation of Jaft is written in JavaScript and uses the concoctor interface. concoctor is fundamentally language-agnostic, but it needs a language of some kind in order to be demonstrated, and the language of choice for that is currently Jaft.