⚠️ STOP. If you arrived here because you were about to apply a lesson, snippet, fact, or tactic learned in one of these two repos to the other — don't. They share zero source-level structure. The only thing in common is the compile target.
Ephapax and AffineScript are two unrelated languages from the same author. They both happen to use type systems with affine-flavored substructural properties — which is true of nearly every modern resource-safe language — and the lexical overlap of the word "affine" is the entire reason agents and humans confuse them.
| Ephapax | AffineScript | |
|---|---|---|
| What it is | Research language for compile-time WebAssembly memory safety | Successor to JS / TypeScript / ReScript |
| Repo | hyperpolymath/ephapax |
hyperpolymath/affinescript |
| Audience | Formal-methods researchers, systems verifiers | Application developers, JS-ecosystem users |
| Surface syntax | .eph (Coq-like / ML-shaped) |
.affine, plus brand-faces: jaffascript / rattlescript / lucidscript / cafescripto / pseudoscript |
| AST/IR | src/ephapax-syntax/src/lib.rs, src/ephapax-ir |
lib/ast.ml, lib/borrow.ml |
| Type checker | ephapax-linear/src/{linear,affine}.rs (Rust, dyadic — see below) |
lib/borrow.ml (OCaml, single discipline with capabilities) |
| Structural rule | Linear (strict) + Affine (companion) — same AST, two checkers | Affine with explicit borrow/copy/drop, NLL-style scopes |
| Binding forms | let-linear, region r in …, regions/exits |
let, borrow/copy/drop, no first-class regions |
| Effects | Region-typed; tracked in type system | Capabilities (io, dom, pixi, tea, …); host-facing |
| Compiler stack | Cargo workspace; Rust + Idris2 + Coq | Dune project; OCaml + ReScript runtime |
| Codegen | typed-wasm via src/ephapax-wasm, formal correctness lemma WIP |
typed-wasm via lib/tw_*.ml, plus Deno-ESM and Node-CJS direct emit |
| Faces / frontends | None — one surface syntax | Many (jaffascript, rattlescript, lucidscript, cafescripto, pseudoscript) — all desugar to the same core |
| Formal semantics | Mechanized in Coq (formal/Semantics.v, formal/Linearity.v) + Idris2 ABI (src/abi/Ephapax/…) |
Operational; soundness arguments in lib/borrow.ml comments + docs/CAPABILITY-MATRIX.adoc; tracking via docs/TECH-DEBT.adoc and issue #177 (CORE-01) |
| Proof obligations | formal/PRESERVATION-HANDOFF.md, ROADMAP.adoc § "Preservation closure plan", PR ephapax#146 |
None mechanized; verification/proofs/ dirs are RSR template scaffolding only |
| Standard library | ephapax-linear/src/, examples/ |
stdlib/, vendor/, bindings (affinescript-dom, affinescript-pixijs, affinescript-tea) |
| Editor support | editors/, idris2/ LSP work |
vscode-ext/, NPM-published @hyperpolymath/affine-vscode |
| Status | Research-active; preservation theorem in flight | Application-active; multiple PRs/release cycle |
Two things, both via hyperpolymath/typed-wasm:
- Both languages compile (or are moving towards compiling) to typed-wasm.
- Both languages interface with the same Rust verifier crate
crates/typed-wasm-verify/inside the typed-wasm repo.
That is the entire shared surface. They are sibling languages standing on a common verification substrate — not an implementation ↔ proof pair, not a parent ↔ dialect pair, not a frontend ↔ backend pair.
Ephapax is itself dyadic. Inside the ephapax repo there are two sublanguages:
- ephapax-linear — the strict, formally-verified core.
- ephapax-affine — a more versatile prototyping companion that works with ephapax-linear.
Both are ephapax-internal. They share one AST, one Rust crate (ephapax-linear/), one grammar directory (ephapax-linear/grammar/{linear,affine}.ebnf). The "affine" in "ephapax-affine" names the structural discipline — one of two type-checking modes in one crate.
ephapax-affine is NOT AffineScript. The word affine overlapping in both names is a coincidence of substructural-logic terminology, not a relationship. AffineScript is a separate language with its own AST, its own borrow checker, its own faces.
Three traps stacked on each other:
- Lexical: both names contain
affine. - Conceptual: both use type systems with affine-logic properties, because nearly every resource-safe language does (Rust, Linear Haskell, F*, Idris with QTT, Granule, …). The mere presence of "affine" in a description is not a fingerprint.
- Single-author: both projects come from the same author, so cross-references and shared idioms appear in commit messages, docs, and chat. Don't read those as evidence of structural relationship.
Quick visual signals, in priority order:
| Signal | Ephapax | AffineScript |
|---|---|---|
| Repo URL | github.com/hyperpolymath/ephapax |
github.com/hyperpolymath/affinescript |
| Build tool | Cargo.toml at root |
dune-project at root |
| Main language | Rust (+ Coq + Idris2) | OCaml (+ ReScript runtime) |
| File extension under work | .eph, .v, .idr |
.affine, .ml, .res |
| Top-level dirs | formal/, idris2/, ephapax-linear/, src/ |
lib/, vendor/, stdlib/, face-directories |
| Headline file | formal/Semantics.v, ROADMAP.adoc |
lib/borrow.ml, docs/CAPABILITY-MATRIX.adoc |
| Open soundness ticket | preservation 11 admits (Brief C / PR ephapax#146) | issue affinescript#177 (CORE-01) |
If you can't tell from the repo URL alone, you are in the wrong context for the work.
Do:
- When switching context between the two, state it explicitly: "switching context from AffineScript to ephapax" (or vice versa). This forces the user-visible audit trail.
- Treat memory entries written about one as inadmissible for the other unless the cross-reference is explicit and recent.
- When you read a snippet mentioning "affine", look at which file path it lives in before assuming you know which language is meant.
- Cross-reference the canonical disambiguation memory entry:
feedback_affinescript_ephapax_siblings_not_impl_proof.md.
Don't:
- Apply borrow-checker tactics from
lib/borrow.ml(OCaml, AffineScript) toformal/Semantics.v(Coq, ephapax) or vice versa. - Re-use proof-obligation framings across the two. Ephapax has Coq/Idris2 mechanized proofs; AffineScript has compiler-correctness arguments.
- Conflate
ephapax-affine(the sublanguage inside ephapax) withAffineScript. They have nothing in common beyond the syllable. - Assume that a typed-wasm-side change in one repo implies anything about the other. The verifier crate is shared, but the consumers are independent.
- Ephapax:
hyperpolymath/ephapax— README.adoc, ROADMAP.adoc, formal/PRESERVATION-HANDOFF.md. - AffineScript:
hyperpolymath/affinescript— README.adoc, docs/ECOSYSTEM.adoc, docs/CAPABILITY-MATRIX.adoc. - Shared target:
hyperpolymath/typed-wasm— README.adoc. Documents both languages as independent producers. - Canonical disambiguation memory entry:
feedback_affinescript_ephapax_siblings_not_impl_proof.md(in the user's auto-memory; durable across sessions).
The canonical statement is the source code. If this doc and the source disagree, update this doc. Keep the disambiguation specific and current:
- If ephapax adds a third sublanguage, update the dyadic-naming-trap section.
- If AffineScript adds a face, update the faces row in the comparison table.
- If the two ever share more than typed-wasm + the Rust verifier, update the "What is shared" section before anything else.
- If a new confusion class emerges (e.g., a third language with
affinein the name), extend this doc rather than fork.
Last revised: 2026-05-26.