Bondy

declarative language · on the BEAM

Bondy is a declarative language for the BEAM, built for multi-agent systems: statically typed, functional, relational and rule-based. The type the compiler checks is the same type that validates data at runtime. It starts where you already are, with functions and processes, and lets a typed value do more than sit in a variable: hold as a relation, stand as a constraint, leave as an event. Logic, time and reactivity live under that one type system instead of being bolted on.

Version0.8 · pre-release
RuntimeBEAM · OTP 29+
LicenseApache-2.0
Code examples

Match the input, pipe the result.

The core is functional, as on the rest of the BEAM: immutable values, functions whose clauses pattern-match their arguments, and pipes that read top to bottom. Every other noun is built on this one.

counts.bondyRuns today01 / 08
module Counts

%% Each clause matches a different input; the first match wins.
function describe(0) -> "none"
function describe(1) -> "one"
function describe(n) when n < 10 -> "a few"
function describe(_) -> "many"

public function describe_all(xs: List(Int)) :: List(String) ->
    xs
    |> List.map(fun(n) -> describe(n) end)
bondy(1)> Counts.describe_all([0, 1, 7, 42])
["none", "one", "a few", "many"]
Why Bondy

Declare the data once. Skip the glue.

On the BEAM a value is a datum you pass around. Everything else it should be — valid, queryable, subject to rules, announced when it changes — you rebuild by hand. Each carries its own copy of the shape, and each drifts from the others. Bondy keeps the functions and processes you have and gives the typed value those jobs directly.

01

The type is the validator

Int & > 0 & =< 10000 is the type, not an annotation. The compiler rejects a bad literal. At runtime the type is a value: & unifies incoming data against it, names each failing field by its path, and holds partial data open until the rest arrives. No changeset, no schema library, no second copy of the rules.

02

No ORM, no query framework

One line makes a Shape a relation. Joins, recursion and transitive closure are then queries over the same typed values your functions and rules use, with no DTO or mapping layer, kept up to date incrementally as facts change.

03

Logic that fires itself

A rule runs when the store makes it true, not when you remember to call it. Rewrite rules match, remove and tell over your Shapes until nothing more applies. Guards that flip as the store grows are a class of bug the compiler rejects before the rule ever runs.

04

Effects and capabilities, checked

The compiler works out what every function may do: files, the network, ETS, messages, the clock. @law(:pure) and @capability are limits it holds the code to, not comments. A rule guard with an effect is rejected, and untrusted code runs with only the capabilities you grant it.

05

Tooling from day one

Compiler, REPL, formatter and bondy new. A rebar3 plugin and a Mix compiler, so it drops into the Erlang or Elixir project you already have.

06

It's still the BEAM

Standard .beam bytecode. Supervision, hot reload, distribution and Erlang/Elixir interop come with the ground you're standing on — from the people who run Bondy Connect and Partisan in production.

The nouns

Five nouns. Four roles. One type system.

Two nouns you already have from the BEAM, and three Bondy adds: the Shape, the rule and the signal. Everything else is a role a Shape plays, declared in one line and checked by the same type system. That is why a relation, a constraint or an event needs no noun of its own.

Noun 01 · foundationFunctions

The functional core.

Immutable data, pure functions, pattern matching — the substrate everything else is built on. No surprises, and that's the point.

Noun 02 · foundationProcesses

OTP, underneath all of it.

BEAM processes host agents, constraint solvers and derivation workers. A constraint store belongs to one process and reaches its fixed point there. Supervision and fault isolation come free.

Noun 03 · the seedShapes

Invalid data fails at compile time, or when it is built.

Types that carry their own refinements — no assert, no second-class validators. The refinement is the type, checked the moment you compile and again when a value is built at runtime.

Noun 04Rules

Rules that fire themselves.

Deductive and reactive. Derivation rules compute new relations from old. Rewrite rules match, remove and tell over a constraint store of Shapes, to a fixed point.

Noun 05 · plannedSignals

Change propagates itself.

A named value defined by an equation and kept equal to it — recomputed when an input moves, glitch-free and ordered by the dependency graph. No manual invalidation.

The roles

One Shape, four roles.

A role says what a Shape is doing at runtime: being held, imposed, emitted or latched. The same Shape can play more than one, relation Bucket and constraint Bucket on two lines, and unification works in all of them, which is how a rule head and a query match the same fact.

Role 01 · heldRelations

Ask questions of your data.

Declare a Shape a relation and it becomes a set of facts: queries, joins and transitive closure, incrementally maintained as facts change.

Role 02 · imposedConstraints

Say what must hold.

Declare a Shape a constraint and it can be told to a store, asked of it, and rewritten by rules. Validation is the special case where you tell everything and ask once.

Role 03 · emittedEvents

Say what happened.

Declare a Shape an event and rules can emit it. It is how a fixed point speaks to the rest of the system: typed, addressed to nobody in particular, never stored.

Role 04 · latched · plannedFluents

Truth that varies over time.

Declare a Shape a fluent and its truth persists between the rules that start and stop it, queryable at any instant. Connection up, quota spent, session live.

It runs on the BEAM you already trust.

Bondy compiles to standard BEAM bytecode, so it inherits what the BEAM gives every language on it. Millions of lightweight processes, each with its own heap and garbage collector, so one busy process never pauses the rest. Supervision trees that restart what fails. Distribution across nodes by message passing, and hot code loading without stopping the system. Bondy calls Erlang and Elixir, and is called from them, directly. The documentation is published with the release.

Early access

Not public yet. Come talk to us.

Bondy will be made public at Code BEAM Europe on 21st October. Until then, read the Saying What series on the blog, which walks through the language one noun at a time. We design will continue in the open on Zulip: ask about the nouns, argue with the roadmap, and hear first when the toolchain opens.