NoiseLang: Where Every Value is a Probability Distribution
Manu Martínez-Almeida has resurrected a nine-year-old side project called NoiseLang, a probabilistic programming language where every value is a probability distribution. The language compiles to a Cranelift JIT or WASM backend, delivering 5.8 billion samples per second on a 14-core M4 Pro. The project was originally conceived during a telecommunications degree but never finished until AI tools helped build the runtime.
Core Idea: Everything is a Distribution
NoiseLang's fundamental concept is that every value is a probability distribution. A plain number like 5 is a Dirac delta—a spike at that value. Random variables like X ~ unif_int(1, 6) are also distributions, and operators work on distributions directly. For example, X + X is 2X (same draw), while independent draws require ~ multiple times or ~[N] for vectors.
Nothing executes until a query like P(X + Y < 10) is made. At that point, the runtime runs millions of simulations across all cores and returns an estimate with a standard error. The birthday paradox is a one-liner:
Bday = unif_int(1, 365)
days ~[23] Bday
P(has_duplicates(days)) # about 0.507
Architecture: One IR, Three Backends
Under the hood, the language builds an append-only DAG called the RvGraph. This graph is compiled into three code paths:
- A columnar batch interpreter (correctness oracle)
- A Cranelift JIT that fuses expressions into native kernels
- A WASM emitter for browser execution
All backends produce bit-identical results. If a backend cannot compile an expression, it falls back to the interpreter.
Performance: 5.8 Billion Samples/Second
The Monte Carlo loop is optimized with kernel fusion, keeping intermediate values in registers. The PRNG (xoshiro256++) compiles into the kernel, and math functions like ln, sin, cos become inline polynomial approximations. A clever trick runs four independent RNG streams in parallel to hide serial dependencies, beating a hand-written SIMD kernel.
On a 14-core M4 Pro, a one-line P(...) sustains ~5.8 billion samples per second and scales ~9.6× from one core to all. Per core, the generated kernel runs within ~1.15× of hand-written Rust compiled by LLVM. WASM runs at half to three-quarters of native speed inside V8.
Comparison to NumPy, Stan, PyMC
NoiseLang is a toy language, not for production. It fills a niche: rapid prototyping of probability questions. NumPy requires writing the simulation manually; Stan and PyMC use HMC/NUTS samplers for posterior fitting. NoiseLang provides forward Monte Carlo with rejection-based conditioning, ideal for a handful of discrete observations but not for thousands of continuous measurements.
Real-World Example: AM vs FM
A demonstration models why FM survives noisy channels better than AM. The message is a sine wave, modulated into amplitude or frequency of a complex carrier. Identical white noise is added. AM demodulation reads the length (corrupted by noise), while FM reads the angle (noise barely changes it). The result: FM error is ~5× lower, and in the small-noise limit the advantage approaches swing² = 9.
Browser Usage
NoiseLang runs in the browser via WebAssembly. Install with npm install @noiselang/core and use:
import { run } from "@noiselang/core";
const result = await run(`
X ~ rand::unif(-1, 1);
Y ~ rand::unif(-1, 1);
4 * P(X^2 + Y^2 < 1)
`);
console.log(result.value); // ~3.1415
Why It Sat for Nine Years
The design was easy—a parser and tree-walking interpreter is a weekend project. The hard part was building an efficient Monte Carlo runtime, conditional Bayesian inference, and multiple backends. AI agents helped build the JIT, runtime, and numerical code, but struggled with language design decisions.
Conclusion
NoiseLang is not for production, but it's a fascinating exploration of probabilistic programming. Use it for whiteboard-stage questions, then switch to Stan/PyMC for posteriors or NumPy/JAX for production. Try it in the browser or with Node.js—it's a toy worth playing with.





