beni is a functional programming language that compiles to JavaScript.
It is also a research project. Languages that compile to JavaScript usually ship code that works things out while the program runs: what changed on the page, whether a value is shared, whether a function waits, whether data has the right shape.
beni's compiler works these out before the program runs, and only what can't be known in advance is left to run time.
This is possible because of the language's guarantees: values are immutable, types are sound, effects are tracked, and the compiler sees the whole program.
A complete app
A counter with two buttons.
type alias Model =model{ "count": 0 }
count : Int
type Msg
= Inc
| Dec
update : Msg, Model → Model
update msg model =
case msg of
Inc →
{ model | count = model.count + 1 }
Dec →
{ model | count = model.count - 1 }
view : Model → Html Msg
view model =
built-in JSX compiler<div>
<button onClick={Dec}>-</button>
<output>{model.count}</output>
<button onClick={Inc}>+</button>
</div>
main : Tea.Program
main = Tea.sandbox { init = λ⊤ → { count = 0 }, update = update, view = view }
running
It compiles to 419 bytes of JavaScript after compression.
1 / 4
Compiled to vanilla JavaScript
beni's compiler turns your program into what carefully hand-tuned vanilla JavaScript would look like. The output is fast and small.
beni ships a minimal runtime, and each component adds very little to the bundle, so the app stays small as it grows.
The counter above compiles to 419 bytes after compression.
2 / 4
Immutability
Values are immutable, but a List is a plain JavaScript array. When nothing reads the old list, the compiler edits it in place. When something does, the program doesn’t compile, and the message shows the fix.
The ownership rules are being proved in Lean.
3 / 4
Effects
The compiler infers which functions can wait on a file, the network or a timer. Those run on beni’s fiber runtime, the rest compile to plain JavaScript functions. There is no async or await in the source.
size path =
case Io.readFile path of
Ok text → String.length text
Err _ → 0
The fiber runtime is only included when a program can wait.
4 / 4
Validation
A schema describes your data, and the compiler turns it into a parser for exactly that shape. No validation library is shipped. A failure is a list of issues, never an exception.
schema User =
name : String
email : String
age : Int optional
main =
case User.parse text of
Ok user → Node.print user.name
Err issues → Node.printLines (List.map issues describe)
const name = value.name;
if (name === undefined) return fail("name", "missing key \"name\"");
if (typeof name !== "string") return fail("name", "expected a string");
const email = value.email;
...
return { name, email, age };
The same schema also gives a printer, User.print, that writes the value back to JSON.
Output size
A build contains only the code its program uses.
- 419 B
- The counter above
- 985 B
- Validate a User and report its issues
- 2.2 kB
- Read a file, parse it, print a field (includes the fiber runtime)
Size after brotli
Guarantees
- No runtime exceptions.
- Every case handles every possibility.
- Every failure of a host API comes back as a typed value.
- When the compiler refuses a program, the message names the problem and a fix.
Platforms
A beni program is built for a platform: the browser, Node or Cloudflare Workers today. The platform defines what a program is and gives it access to the host.
Platforms are the only place where beni code meets JavaScript. Each host API is wrapped there, and each failure it documents becomes a typed error.
A platform also decides how markup compiles. On the server, a view renders to an HTML string. In the browser, the same view compiles to templates and update functions. The compiler has no built-in knowledge of HTML.
The design of platforms and applications comes from Roc.
The compiler
- Written in Zig.
- Files are parsed in parallel, and modules are checked in parallel along their dependencies.
- Rebuilds are incremental.
- The output is the same byte for byte, whatever the number of threads.
- The same compiler runs in the browser as WebAssembly. The playground uses it.
Research
Each part of beni is specified before it is built, and its measurements are published.
beni is early. It compiles working programs for the browser, Node and Cloudflare Workers, but the language is still changing.
Playground
The compiler runs in the browser. Write beni and see the JavaScript it produces.
Coming soon