Tree @develop-0.2 (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 : 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).- tuples may be created, and destructured, with the
[...]syntax.
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
Language:
- Supports
!==operator. - Officially does not support
!=and==operators. - Added
[...]syntax for creating and destructuring tuples, intended mainly to support multiple return values from functions.
Implementation:
- Uses version 0.2 of concotor, in particular the new, simpler and much less naff interface that is exposed to compilers.
- concoctor-level unit tests were moved to concoctor repo;
jaftConcoctor-specific tests moved to own test suite. - Improves
jaftito 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.
Commit History
@develop-0.2
git clone https://git.catseye.tc/Jaft/
- Pivot from supporting multiple return values to supporting tuples. Chris Pressey 5 days ago
- Do not try to reckon tail position directly in the parser. Chris Pressey 7 days ago
- Initial work on supporting multiple return values. Chris Pressey 9 days ago
- Allow `jafti` to evaluate either an expression or a function. Chris Pressey 14 days ago
- Update README. Chris Pressey 14 days ago
- Use concoctor 0.2 (official tag). Chris Pressey 14 days ago
- Binary operators: support !==; == and != officially not supported. Chris Pressey 15 days ago
- Use new concoctor interface. Chris Pressey 18 days ago
- Move jaftConcoctor-specific tests to jaftConcoctor.test.js. Chris Pressey 19 days ago
- Fix link to concoctor documentation. Chris Pressey 21 days ago