Tree @0.1 (Download .tar.gz)
Jaft
Version 0.1 - 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. 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. 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 : falseExprsyntax. - scoped
let ident = expr1, ident2 = expr2; exprsyntax. Note, unlike JavaScript syntax, this is an expression and can e.g. occur inside parentheses. const ident = expr1, ident2 = expr2; exprsyntax. Note that because all bindings are immutable, this is functionally identical tolet, but if an identifier is bound withconstthen 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.
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.)
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 demonstated and tested, and the language of choice for that is currently Jaft.
Commit History
@0.1
git clone https://git.catseye.tc/Jaft/
- Small changes, to use concoctor 0.1, in prep for release of 0.1. Chris Pressey 22 days ago
- Use external concoctor package (hosted on Codeberg currently.) Chris Pressey a month ago
- Reach another milestone in disentangling concoctor from Jaft. Chris Pressey a month ago
- Merge branch 'master' of https://codeberg.org/cpressey/Jaft Chris Pressey a month ago
- Add motivation / case study. Chris Pressey a month ago
- Keep rewriting the readme. Move TODO items to their own document. Chris Pressey a month ago
- A few small edits for more clarity Chris Pressey a month ago
- Small edits to readmes. Chris Pressey a month ago
- Rewrite explanation in README, yet again. Chris Pressey a month ago
- Rename specification document. Chris Pressey a month ago