Loose equality: AST admission, deferred FJS support

Status: syntax AST implemented; FAST lowering deferred

a == b and a != b are retained in syntax and resolved source AST as ['==', a, b] and ['!=', a, b], respectively, with JavaScript equality precedence and left associativity. The language designer approved AST admission and deferred FJS support in the working session. AST-to-FAST conversion explicitly fails, even for unreachable occurrences. The TODO tracks semantics and lowering. Neither operator is rewritten to its strict counterpart.

JavaScript's == and != convert their operands before comparing them, by rules that depend on both types: 0 == "" and 0 == "0" are true while "" == "0" is false, and null == undefined is true while null == 0 is false. A reader cannot tell what a comparison does without knowing both types, so AST admission alone does not enable their execution. === and !== compare without converting, and a conversion that is meant is written, Number(x) or String(x).

The row op.loose-equality of refused constructs registers the refusal, and operators gives its reason.

Examples

/** @type {(a: unknown, b: unknown) => boolean} */
const same = (a, b) => a == b;
export default same(0, "");

The demo compiles sample.mjs, the module above, and displays the AST-to-FAST conversion failure. Below that it shows what JavaScript answers for the same module: true.

What to Write Instead

Compare with ===, and write the conversion you mean, Number(b) or String(a), as alternative.f.js does:

/** @type {(a: number, b: string) => boolean} */
const same = (a, b) => a === Number(b);
export default same(0, "");

It answers true. "" converts to 0, the conversion == made, now written where a reader sees it.

A Better Message

Today fjs compile prints == AST-to-FAST conversion is not implemented for the module. The demo proposes additional guidance: the reason for the module, what JavaScript does there, and the allowed spelling, the line alternative.f.js writes, as help. The page compiles the alternative too, so the suggestion is one the compiler accepts.

Reference

Demo

Not allowed in FunctionalScript

This is valid JavaScript, and fjs compile refuses it, by design: it is not a FunctionalScript module.

What fjs compile sample.mjs output.js prints:

sample.mjs - error: == AST-to-FAST conversion is not implemented

What it should print instead, pointing to what FunctionalScript allows (proposed):

sample.mjs - error: `==` lowering is not implemented; its AST retains JavaScript loose equality
 = help: compare with `===`, and write the conversion you mean, `Number(b)` or `String(a)`:
         const same = (a, b) => a === Number(b);
 = note: AST admitted; lowering tracked in spec/todo/loose-equality.md

JavaScript source, sample.mjs:

/** @type {(a: unknown, b: unknown) => boolean} */
const same = (a, b) => a == b;
export default same(0, "");

What JavaScript answers for the same module, its default export:

true

What to write instead

The FunctionalScript spelling, alternative.f.js:

/**
 * The FunctionalScript spelling of [`sample.mjs`](./sample.mjs): the conversion `==` makes in secret, written, and the comparison `===`, which converts nothing
 * ([`README.md`](./README.md)).
 *
 * @module
 */

/** @type {(a: number, b: string) => boolean} */
const same = (a, b) => a === Number(b);
export default same(0, "");

What fjs compile alternative.f.js output.js writes:

const $0=($1,$2)=>$1===Number($2);export default $0(0,"");

What it answers, its default export:

true

Emergent Testing

Idle. Press Run to start the suite.

  • ./proof.f.mjs