This document records the key design decisions for AffineScript’s implementation.
Decision: Minimal runtime, rely on host for most services
-
Runtime is small and focused on AffineScript-specific needs
-
No garbage collector for most data (ownership handles memory)
-
Small tracing GC only for cyclic ω-quantity data (opt-in)
-
Host provides: I/O, networking, filesystem, timers
Rationale: Keeps WASM binaries small, maximizes portability, leverages host capabilities.
Decision: Evidence-passing (Koka-style)
-
Effects compiled via evidence-passing transformation
-
Each effect operation receives an “evidence” parameter
-
Handlers install evidence at runtime
-
One-shot continuations optimized to avoid allocation
Rationale: Better performance than CPS, proven in Koka, good balance of complexity/speed.
Implementation:
// Source
handle computation() with {
return x → x,
get(_, k) → resume(k, state)
}
// Compiled (evidence-passing)
computation({
get: (ev, k) => k(state)
})
Decision: WASM core + WASI, with Component Model readiness
| Feature | Decision |
|---|---|
WASM Core |
✅ Required baseline |
WASM GC |
❌ Not required (ownership handles memory) |
WASI |
✅ For CLI/server use cases |
Component Model |
✅ Design for future compatibility |
Threads |
Rationale: Broad compatibility now, future-proofed for Component Model.
Decision: Z3 as optional external dependency
-
Z3 bindings (ocaml-z3) for refinement type checking
-
SMT is optional: refinements work without it (runtime checks)
-
Can be disabled for faster compilation
-
Future: support CVC5 as alternative
Configuration:
# affinescript.ncl
{
smt = {
enabled = true,
solver = "z3",
timeout_ms = 5000,
}
}Decision: Workspace-aware, Cargo-inspired
-
Single
affine.tomlmanifest per package -
Workspace support for monorepos
-
Lock file for reproducibility
-
Content-addressed storage (like pnpm)
-
Written in Rust
Manifest format:
[package]
name = "my-project"
version = "0.1.0"
edition = "2024"
[dependencies]
std = "1.0"
http = { version = "0.5", features = ["async"] }
[dev-dependencies]
test = "1.0"Decision: Small core + blessed packages
Core (always available): - Prelude: Basic types, traits, operators -
Option, Result: Error handling - List, Vec, Array: Collections
- String, Char: Text
Blessed Effects (in std): - IO: Console, file system - Exn:
Exceptions - State: Mutable state - Async: Async/await - Reader:
Environment access
Community Packages: - HTTP, JSON, databases, etc. - Not in std, but curated/recommended
Decision: Long-term goal, not immediate priority
-
Phase 1-4: OCaml compiler
-
Phase 5+: Gradually rewrite in AffineScript
-
Start with: lexer, parser (simpler)
-
End with: type checker, codegen (complex)
Timeline: After 1.0 stable release
Decision: JavaScript first, Rust second
| Target | Priority | Method |
|---|---|---|
JavaScript |
🥇 Primary |
wasm-bindgen, host bindings |
Rust |
🥈 Secondary |
Native FFI for tools |
C |
🥉 Tertiary |
Via Rust FFI |
Rationale: WASM’s primary deployment is web/JS; tooling benefits from Rust.
Decision: Rust-style elaborate diagnostics
-
Multi-line errors with source context
-
Color-coded by severity
-
Suggestions for fixes
-
Error codes with documentation links
-
Machine-readable JSON output option
Example:
error[E0312]: cannot borrow `x` as mutable because it is already borrowed --> src/main.afs:12:5 | 10 | let r = &x; | -- immutable borrow occurs here 11 | 12 | mutate(&mut x); | ^^^^^^ mutable borrow occurs here 13 | 14 | use(r); | - immutable borrow later used here | = help: consider moving the mutable borrow before the immutable borrow
Decision: Refinement types with SMT only (no interactive proving)
-
Refinements checked via SMT solver
-
No tactic language or proof terms
-
Proofs are erased (quantity 0)
-
Future: optional Lean/Coq extraction for critical code
Rationale: Practical verification without complexity of full theorem prover.
Decision: Priority order
-
Web applications (frontend + backend)
-
CLI tools
-
Libraries/packages
-
Embedded/WASM plugins
-
Scientific computing (future)
Decision: Benevolent dictator initially, open governance post-1.0
-
Pre-1.0: Core team makes decisions quickly
-
Post-1.0: RFC process for major changes
-
Open source from day one (Apache-2.0 OR MIT)
-
Community contributions welcome
Decision: Breaking changes during 0.x, strict semver from 1.0
-
0.x releases may break compatibility
-
Migration guides for breaking changes
-
1.0+ follows strict semver
-
Edition system for language-level changes (like Rust)
| Layer | Technology | Notes |
|---|---|---|
Compiler |
OCaml 5.1+ |
Existing codebase |
Parser |
Menhir |
Existing |
Lexer |
Sedlex |
Existing |
SMT |
Z3 (ocaml-z3) |
Optional |
Runtime |
Rust |
WASM target |
Allocator |
Custom (Rust) |
Ownership-optimized |
Package Manager |
Rust |
CLI tool |
LSP Server |
Rust |
Performance |
Formatter |
OCaml |
Shares AST |
REPL |
OCaml |
Interpreter mode |
Web Tooling |
ReScript + Deno |
Per standards |
Build Meta |
Deno |
Per standards |
Config |
Nickel |
Per standards |
Docs |
Custom generator |
From types |
| Purpose | Format | Extension |
|---|---|---|
Source code |
AffineScript |
|
Package manifest |
TOML |
|
Lock file |
TOML |
|
Configuration |
Nickel |
|
Build scripts |
Deno/TS |
|
Documentation |
Markdown |
|