Skip to main content

Design Notes

Answers to the "but why?" questions, condensed from the dev log and a draft paper on Raven's design.

Why WebAssembly first?

Unusually, Raven prioritises WebAssembly over native machine code. Wasm gives us portability (compile once, run on anything with a wasm runtime), security (run untrusted code without fear – it's the sandbox browsers bet on), and reach: for a growing share of the world, a web browser on a phone is the computer, and languages that need a native toolchain installed simply can't get there. Native execution is still available the boring way – wasm runtimes JIT to native speed, and Bun can pack a Raven program into a standalone binary.

A dynamic language without escape hatches

Dynamic languages have always kept an escape hatch: when static knowledge runs out, a JIT deoptimises to an interpreter, or values fall back to a uniform boxed representation. That machinery is at odds with where code needs to run now – sandboxed and embedded targets reward small ahead-of-time binaries, fast cold starts, and predictable resource use, and often rule out run-time code generation entirely.

Raven removes every hatch at once: no interpreter to fall back to, no run-time code generator, no universal boxed representation. The consequence is that type inference can't ever give up – it must produce a usable, layout-determining answer for every program, annotated or not. A lot of Raven's design falls out of taking that constraint seriously: unions that widen into precise recursive types rather than truncating to Any, and flow typing that arises from partially evaluating the language's own pattern-matching code, so it's extensible by libraries and sound by construction.

The payoff is that "dynamic-feeling" code – no annotations, ADTs built on the fly, dispatch everywhere – compiles to the kind of static code you'd expect from a systems language.

Why multiple dispatch?

Dispatch is the polymorphism mechanism (there are no classes). It composes better than methods-on-classes: functions don't privilege their first argument, and anyone can extend anyone else's function with new types – the expression problem dissolves. Julia demonstrated how well this works for numeric and scientific code; Raven inherits that lineage while aiming at more general programming. And because signatures are patterns, dispatch, destructuring and control flow are all one mechanism rather than three.

Why value semantics?

Shared mutable state is the root of a great deal of confusion: a change made through one reference shows up through another, and suddenly you need to think about identity, aliasing, defensive copies and locks. Raven's data is immutable values, with the swap operator recovering the convenience of mutation (updating a variable in place) without the aliasing. The compiler's reference counting makes this efficient – values used uniquely are updated in place, so the functional style doesn't cost copies.

Reference counting over tracing GC is itself a considered choice: deterministic lifetimes free resources (memory, but also file handles, GPU buffers) the moment they're done, keep memory use predictable for embedded and sandboxed targets, and enable that in-place reuse.

Why no async/await?

Function colouring splits a language in two: sync functions can't call async ones, so async spreads virally through every signature. Raven's runtime can suspend any code (wasm's stack-switching support makes this practical), so await is just a function and no function is a different colour. Concurrency becomes a library concern – tasks and channels – rather than a type-system one.

Why another language at all?

The honest answer: the web platform deserves a language that's native to it. JavaScript is entrenched but weakly typed and full of legacy; TypeScript checks it but can't change the runtime; compiled-to-wasm languages (Rust, Go, C#) bring heavyweight toolchains and impedance with JS. Raven tries to be the language you'd design for this platform: scripting-language ergonomics, systems-language output, JS interop without glue, and sandboxing for free.

Comparisons people ask about

  • Julia – Raven's closest relative in spirit: multiple dispatch, numeric tower in the library, dynamic feel with compiled speed. Raven trades Julia's JIT (and its latency and heft) for ahead-of-time wasm, and replaces mutable arrays with value semantics.
  • Rust / Zig – similar ambitions for output (small, fast, no GC pauses), completely different ergonomics: Raven has no lifetimes or borrow checker to satisfy; the compiler inserts reference counting for you.
  • TypeScript – Raven is a language with inference all the way down rather than annotations over JS semantics – but it's designed to live in the same projects, calling and being called by TS/JS.
  • Gleam / Roc / Elm – fellow young functional-flavoured languages; Raven is less doctrinaire about purity (swap arguments, ! functions) and more invested in JS interop and dispatch.