Principles

May 7, 2026 · View on GitHub

The decisions behind Awsum's design, grouped by what the language guarantees vs how the compiler resolves trade-offs when those guarantees push against each other.

What the language guarantees

  1. Identical results on every target. The same program produces the same stdout whether compiled for LLVM, JVM, CLR, WASM, or JS — identical, verified by the test suite on every commit. A compiler invariant, not a best-effort goal; the rest of the design hangs off it.

  2. Stack safety belongs to the compiler, not the user. Recursion — tail, non-tail, self, mutual — is normalised at Core level into a shape that runs in bounded stack on every backend, including JVM and JS where native cross-method tail calls do not exist. No manual CPS transforms, no tailRecM, no hand-unrolling. Write the recursion that expresses the algorithm; the compiler makes sure it doesn't fall over. See recursion.md.

  3. Effects are platform-aware, and effects are values. The compiler tracks which effects each target supports — a program using Terminal won't compile for a browser target; a program using Window won't compile for CLI. "Not supported" is a compile error, never a runtime surprise. Effects compose through IO e a: an IO value is data describing the effect, never a side effect performed during construction. Only the IO returned from main is run, by a small per-target runtime walker. The error row e is honest — failure modes appear in the type, not in invisible exceptions.

  4. Errors are values. Arithmetic doesn't silently break. Division by zero, overflow, underflow, precision loss — all represented in the type system via Either. Effects too: IO e a carries its failure type explicitly through the same row mechanism. The programmer decides how to handle them; the compiler cannot "just return zero" or NaN.

  5. No invisible decisions. When something has to be chosen, the programmer chooses — not the compiler. No defaulting of integer literals to a type the compiler guessed (ambiguity is rejected with an explicit hint); no silent shadowing of an outer name by an inner one (shadowing is a compile error); no quiet reference to something the programmer marked as unused (_-prefixed bindings cannot be read). The rules are small; the shared root is that nothing meaningful is decided behind the reader's back.

How the compiler resolves trade-offs

When two of the language guarantees push in different directions, the compiler picks in this order:

  1. Correctness — no runtime exceptions, no platform-behaviour differences, no surprising overflow / underflow / string corruption.
  2. Runtime performance — generated programs should be fast.
  3. Compilation speed — the compiler itself can be slower if it produces better output.
  4. Syntactic convenience — the source can be more verbose if that's what it takes to keep the guarantees above visible.