{
    "version": "https://jsonfeed.org/version/1",
    "title": "Bondy — Dispatches",
    "home_page_url": "https://bondy.io/",
    "feed_url": "https://bondy.io/feed.json",
    "description": "Ideas, design notes, specs in progress and implementation stories from the people building Bondy: the language, the platform and the protocols between them.",
    "icon": "https://bondy.io/feed-icon-144.png",
    "items": [
        {
            "id": "https://bondy.io/posts/2026-10-12-one-module-five-nouns",
            "content_html": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>Here is the whole thing. Seventy lines, one module and its manifest, compiled on the current toolchain. Walk it top to bottom and every construct the series introduced that the compiler has today appears once.</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> RateLimiter</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> implements</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Event</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> | </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@moduledoc</span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\"> \"\"\"</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">A token bucket rate limiter.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">\"\"\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.{</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">NoReply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Constants, evaluated at compile time. A public one is exported.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> const</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> default_plan</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\"> \"free\"</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">const</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> key_pattern</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\"> \"^</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">[</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">A-Za-z0-9_-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">]</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">{1,64</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">}</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">$\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% A structural type whose field carries a refinement.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Config</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =&#x3C;</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10000</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Relation role: facts you look up.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Plan</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">fact</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"free\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">10</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">fact</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"pro\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,  </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">1000</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Constraint role: facts you impose on the store.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Request</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Event role: what the store tells the world.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Denied</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Rewrite rules over the store.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> consume_token</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">         Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> deny_when_empty</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% The process that owns the store.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> init</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_args</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_ctx</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_ctx</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)  </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(_, _)                 </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> NoReply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(__MODULE__, [], [])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Functions.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> plan_limit</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    query</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> create_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">server</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cfg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">tell</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        server</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cfg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.max_tokens, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cfg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.max_tokens)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    )</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">server</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    case</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Re</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">matches</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">key_pattern</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">of</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        true</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  -></span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">tell</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">server</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        false</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:invalid_key</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p>The project describes itself in <code v-pre>manifest.bcl</code>:</p>\n<div class=\"language-bcl vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bcl</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Library</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">manifest</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Library</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Manifest</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    project</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:limiter</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        version</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"0.1.0\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        description</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"A token bucket rate limiter\"</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><h2 id=\"the-nouns-in-order-of-appearance\" tabindex=\"-1\">The nouns, in order of appearance <a class=\"header-anchor\" href=\"#the-nouns-in-order-of-appearance\" aria-label=\"Permalink to &quot;The nouns, in order of appearance&quot;\">&ZeroWidthSpace;</a></h2>\n<p><strong>Constants.</strong> <code v-pre>default_plan</code> and <code v-pre>key_pattern</code> are evaluated when the module compiles, not when it runs. <code v-pre>default_plan</code> is <code v-pre>public</code>, so another module reads it as <code v-pre>RateLimiter.default_plan</code>. <code v-pre>key_pattern</code> is private. <code v-pre>request</code> checks an id against it with <code v-pre>Re.matches</code> before it tells the store, and a malformed id gets <code v-pre>Error(:invalid_key)</code>. A constant must be an expression the compiler can evaluate, so the pattern is kept as a string and compiled when it is used.</p>\n<p><strong>Functions.</strong> <code v-pre>plan_limit</code>, <code v-pre>create_bucket</code>, <code v-pre>request</code>. Functions over immutable values, pattern matching in the heads, <code v-pre>let</code> bindings that shadow rather than mutate: the functional core you already have on the BEAM, spelled differently. The compiler also knows what each of them may do, which is why <code v-pre>t &gt; 0</code> and any function you call from a guard are checked. The <a href=\"./2026-10-12-what-a-function-is-allowed-to-do.html\">seventh post</a> explains how.</p>\n<p><strong>Shapes.</strong> <code v-pre>Config</code> is a structural type from the <a href=\"./2026-10-12-the-type-that-validates.html\">third post</a>: a named <a href=\"/glossary/shape.html\">shape</a> whose field carries its own refinement. Write <code v-pre>Config(max_tokens: -3)</code> and the compiler rejects it. Build one from a runtime value and the constructor checks the same rule. <code v-pre>Bucket</code>, <code v-pre>Request</code>, <code v-pre>Allowed</code> and <code v-pre>Plan</code> are structural types too, each declared directly in the role it plays.</p>\n<p><strong>Relations.</strong> <code v-pre>Plan</code> is held as facts, and <code v-pre>plan_limit</code> asks it a question with <code v-pre>query</code>. The <a href=\"./2026-10-12-ask-questions-of-your-data.html\">sixth post</a> is about this role and the derivation rules that run over it. The limiter only needs the facts, so there is no <code v-pre>:-</code> here. To run <code v-pre>plan_limit</code>, <code v-pre>Plan</code> also needs a relation store, which that post shows. The module leaves it out to stay short. Add <code v-pre>relation Includes(plan: String, feature: String)</code> and a recursive rule and you have the transitive queries that post replaces.</p>\n<p><strong>Constraints.</strong> <code v-pre>Bucket</code> and <code v-pre>Request</code> are facts in the store that the rules act on. What a bucket is, and what happens to it, are declared next to the type, not spread across a <code v-pre>handle_call</code>.</p>\n<p><strong>Events.</strong> <code v-pre>Allowed</code> and <code v-pre>Denied</code> are values that leave. They exist for one round of the rules, are seen by <code v-pre>handle_event</code>, and are swept away. The <a href=\"./2026-10-12-the-process-that-owns-the-store.html\">fifth post</a> explains the sweep, and why it separates the rule that emits from the process that decides who hears.</p>\n<p><strong>Rules.</strong> <code v-pre>consume_token</code> and <code v-pre>deny_when_empty</code> are from the <a href=\"./2026-10-12-rules-that-fire-themselves.html\">fourth post</a>: they match several facts at once, have guards, and are fired by the store rather than called by you. <code v-pre>rid</code> in both patterns is the join.</p>\n<p><strong>Processes.</strong> <code v-pre>implements GenReactor(...)</code>, <code v-pre>init</code>, <code v-pre>handle_event</code>, <code v-pre>start_link</code>. A supervised OTP process whose state is the store and whose logic is the rules. The <a href=\"./2026-10-12-the-process-that-owns-the-store.html\">fifth post</a> is about this noun, and how it compares with the gen_statem you would otherwise write.</p>\n<p><strong>The manifest.</strong> <code v-pre>limiter</code> is a library: <code v-pre>RateLimiter</code> runs under the supervisor of whichever application starts it, so the manifest names no <code v-pre>start_module</code>. The toolchain generates the <code v-pre>.app.src</code> from it. The <a href=\"./2026-10-12-the-manifest-is-a-module.html\">ninth post</a> shows what an application manifest adds, and how a manifest's types reach the code and the releases.</p>\n<p><strong>Fluents and signals.</strong> Not in this module, because they are not in the compiler yet. The <a href=\"./2026-10-12-the-alarm-that-cleared-itself.html\">eighth post</a> is the design. When they land, the limiter grows a <code v-pre>signal load := ...</code> for how full each bucket is and a fluent for &quot;bucket exhausted since t&quot;, and it stays one module.</p>\n<h2 id=\"running-it\" tabindex=\"-1\">Running it <a class=\"header-anchor\" href=\"#running-it\" aria-label=\"Permalink to &quot;Running it&quot;\">&ZeroWidthSpace;</a></h2>\n<p>From Erlang, because that is where most readers will call it from first:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Pid} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> 'Bondy.RateLimiter'</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">Cfg </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">'Bondy.RateLimiter.Config'</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">3</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">'Bondy.RateLimiter'</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">create_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Pid, &#x3C;&#x3C;</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"key\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">>>, Cfg),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">'Bondy.RateLimiter'</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Pid, &#x3C;&#x3C;</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"key\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">>>).</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% => {ok, {'Bondy.RateLimiter.Allowed', &#x3C;&#x3C;\"key\">>, 2}}</span></span></code></pre>\n</div><p>Compiled modules are named <code v-pre>Bondy.&lt;Module&gt;</code>. A value of a structural type is a tagged tuple at runtime, with one element per field in declaration order. So <code v-pre>Cfg</code> and the reply read the way they do, and you can pattern match on them from Erlang without a header file. Building the <code v-pre>Config</code> on the Erlang side skips its refinement check, so build it in Bondy when the value is untrusted.</p>\n<h2 id=\"getting-it\" tabindex=\"-1\">Getting it <a class=\"header-anchor\" href=\"#getting-it\" aria-label=\"Permalink to &quot;Getting it&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The toolchain is a compiler, a REPL and a project scaffold, with rebar3 and Mix integration, on Erlang/OTP 29 or newer. It opens on 21 October, when I show Bondy at Code BEAM Europe. Until then, <a href=\"https://bondy.zulipchat.com/join/fxufyeorepkntttuwtqiqhgx/\" target=\"_blank\" rel=\"noreferrer\">join us on Zulip</a>: #announcements is where it lands first, and the design discussion is already there.</p>\n<h2 id=\"where-the-series-leaves-off\" tabindex=\"-1\">Where the series leaves off <a class=\"header-anchor\" href=\"#where-the-series-leaves-off\" aria-label=\"Permalink to &quot;Where the series leaves off&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Ten posts. The claim was that a mainstream language gives a value one role and makes you tangle every other role into your code by hand. The fix it proposed was to let one kind of value play several roles, each with its own verbs, under one type system. The program above is the claim with a compiler behind it: shapes describe the values, and each role takes one line to declare. Rules and processes sit beside the functions, and the compiler knows what every function may do.</p>\n<p>One role and one noun are still design: fluents and signals. The distribution story is OTP's, with Partisan next. The compiler checks that rule guards cannot flip from true to false, but not yet that a rule set terminates. I have tried to say each of those where it applied rather than once at the end. That was deliberate: a language that promises more than it compiles is the thing I wrote this series against.</p>\n<p>Code BEAM is in a few days. Come and see it run.</p>\n",
            "url": "https://bondy.io/posts/2026-10-12-one-module-five-nouns",
            "title": "One module, five nouns",
            "date_modified": "2026-10-12T00:00:00.000Z",
            "author": {
                "name": "Alejandro M. Ramallo"
            },
            "tags": [
                "Language"
            ]
        },
        {
            "id": "https://bondy.io/posts/2026-10-12-the-manifest-is-a-module",
            "content_html": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>An Erlang application describes itself in several files. The <code v-pre>.app.src</code> says what the application is. <code v-pre>rebar.config</code> says what it depends on. <code v-pre>sys.config</code> and <code v-pre>vm.args</code> say how a release runs it. Nothing checks that the values in <code v-pre>sys.config</code> are the ones the application expects. The only description of the configuration an application accepts is the code that reads it.</p>\n<p>Bondy puts all of this in one language, the Bondy Config Language (BCL). Apart from its imports, a BCL document holds three things: type definitions, <code v-pre>let</code> bindings for reuse, and top-level bindings, which are the document's output. The types are Bondy <a href=\"/glossary/shape.html\">shapes</a>, and <code v-pre>&amp;</code> is the same <a href=\"/glossary/unification.html\">unification</a> that validates data in Bondy code.<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\">[1]</a></sup> So a configuration file has a type, and the compiler checks the file against it.</p>\n<h2 id=\"three-tiers-three-types\" tabindex=\"-1\">Three tiers, three types <a class=\"header-anchor\" href=\"#three-tiers-three-types\" aria-label=\"Permalink to &quot;Three tiers, three types&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Each tier has a type in the standard library. <code v-pre>Library.Manifest</code> describes a library: an OTP application with modules and no supervision tree. <code v-pre>Application.Manifest</code> adds the entry point of the supervision tree and the application environment. <code v-pre>Release</code> puts applications together with flags for the virtual machine and a system configuration.</p>\n<p>A project has a <code v-pre>manifest.bcl</code> at its root, and its <code v-pre>manifest</code> binding unifies with one of the two manifest types. Here is the manifest of <code v-pre>shop</code>:</p>\n<div class=\"language-bcl vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bcl</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% manifest.bcl</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Application</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Config</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    port</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x3C;</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 65536</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 8080</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    pool_size</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    timeout_ms</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .pool_size </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1000</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">manifest</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Application</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Manifest</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    project</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        version</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"0.1.0\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        description</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"An online shop\"</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    start_module</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">App</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.__MODULE__,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    dependencies</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        cowboy</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: { </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">version</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"~> 2.20\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    extra_applications</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: [</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:crypto</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    env</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Config</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><p><code v-pre>Config</code> is the application environment: every field <code v-pre>shop</code> reads, with its type and its default. The <code v-pre>?</code> marks a field as optional, and <code v-pre>= 8080</code> is its default. A default can read another field of the same shape as <code v-pre>.field</code>, so <code v-pre>timeout_ms</code> follows <code v-pre>pool_size</code>.</p>\n<p>The toolchain generates the <code v-pre>.app.src</code> from this document:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">application</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, [</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">description</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"An online shop\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">vsn</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"0.1.0\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">registered</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,[]},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">mod</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">'Bondy.Shop.App'</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,[]}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">applications</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,[</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">bondy</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">cowboy</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">crypto</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">kernel</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">stdlib</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">env</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,[{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">port</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">8080</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">timeout_ms</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">10000</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">pool_size</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">10</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}]},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">modules</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,[]},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">licenses</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,[]}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]}.</span></span></code></pre>\n</div><p>You don't write the <code v-pre>applications</code> list. The toolchain derives it from <code v-pre>kernel</code>, <code v-pre>stdlib</code> and <code v-pre>bondy</code>, the <code v-pre>extra_applications</code>, and the name of each dependency. The <code v-pre>env</code> list holds the defaults of <code v-pre>Config</code>.</p>\n<h2 id=\"a-manifest-that-is-wrong-does-not-build\" tabindex=\"-1\">A manifest that is wrong does not build <a class=\"header-anchor\" href=\"#a-manifest-that-is-wrong-does-not-build\" aria-label=\"Permalink to &quot;A manifest that is wrong does not build&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The types in the standard library say what a manifest must hold. In <code v-pre>Library.Project</code>, <code v-pre>name</code> and <code v-pre>version</code> are required, and <code v-pre>version</code> must match a semantic version pattern. <code v-pre>Application.Manifest</code> requires <code v-pre>start_module</code>. Leave it out, and the manifest does not finalize:</p>\n<div class=\"language-text vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">text</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>Cannot build a complete Application.Manifest: field `start_module` is required and was not given</span></span></code></pre>\n</div><p>Write <code v-pre>version: &quot;one&quot;</code>, and the error names the pattern and the path to the field:</p>\n<div class=\"language-text vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">text</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>Value \"one\" does not match pattern \"^[0-9]+\\.[0-9]+\\.[0-9]+\"</span></span></code></pre>\n</div><p><code v-pre>Library.Manifest</code> is the same type without <code v-pre>start_module</code>, <code v-pre>env</code> and <code v-pre>start_phases</code>. A library has no supervision tree, so its <code v-pre>.app.src</code> has no <code v-pre>mod</code> entry. It can still declare a <code v-pre>Config</code> type for the applications that use it.</p>\n<h2 id=\"the-manifest-is-a-module\" tabindex=\"-1\">The manifest is a module <a class=\"header-anchor\" href=\"#the-manifest-is-a-module\" aria-label=\"Permalink to &quot;The manifest is a module&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The toolchain compiles each <code v-pre>manifest.bcl</code> into a Bondy module. Its name comes from the application name, one segment for each part between underscores: <code v-pre>:shop</code> gives <code v-pre>Shop.Manifest</code>, and <code v-pre>:acme_pricing</code> gives <code v-pre>Acme.Pricing.Manifest</code>. The build compiles this module first, into the application's own <code v-pre>ebin</code>. Then it compiles the application's code, which can import the types of its manifest:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Settings</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Manifest</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.{</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> defaults</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Config</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> tuned</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Config</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">pool_size</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">20</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span></code></pre>\n</div><p><code v-pre>Config()</code> constructs the value with every default: port 8080, a pool of 10 and a timeout of 10000. <code v-pre>Config(pool_size: 20)</code> gives a timeout of 20000, because the default of <code v-pre>timeout_ms</code> reads <code v-pre>pool_size</code>. The type in the manifest is the type in the code. There is no second definition to keep in step with the first.</p>\n<p>The order of the build sets one rule. The manifest compiles before the rest of its application, so it must not name a module of that application. The build refuses such a reference by name:</p>\n<div class=\"language-text vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">text</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>manifest.bcl names Shop.Settings, a module of its own application; a manifest is compiled before its application, so declare the type in the manifest</span></span></code></pre>\n</div><p>Without the rule, an incremental build would find the module from the last build, and a clean build would fail. With it, an incremental build fails too, so the result does not depend on what is left in <code v-pre>_build</code>. On a clean build, the reference fails as an unknown module.</p>\n<h2 id=\"what-a-document-is-allowed-to-do\" tabindex=\"-1\">What a document is allowed to do <a class=\"header-anchor\" href=\"#what-a-document-is-allowed-to-do\" aria-label=\"Permalink to &quot;What a document is allowed to do&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A BCL document can call functions of the Bondy standard library, so it can compute a value. The compiler checks the document as one unit, with one allowance: reading environment variables.<sup class=\"footnote-ref\"><a href=\"#fn2\" id=\"fnref2\">[2]</a></sup> The compiler refuses a call that needs any other capability. This document builds:</p>\n<div class=\"language-bcl vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bcl</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cfg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    host</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">System</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">get_env</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"SHOP_DB_HOST\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"localhost\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">uppercase</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"shop\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><p>With <code v-pre>SHOP_DB_HOST</code> unset, <code v-pre>host</code> is <code v-pre>&quot;localhost&quot;</code>. With <code v-pre>SHOP_DB_HOST=db1.internal</code>, it is <code v-pre>&quot;db1.internal&quot;</code>. The compiler refuses a field that calls <code v-pre>File.read/1</code>, and the call never runs:</p>\n<div class=\"language-text vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">text</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>Function cfg/0 requires capabilities not permitted in a safe-mode unit. Its capability set must be within the unit's allowance (the capabilities the host granted this unit). Route effectful work through a granted capability.</span></span></code></pre>\n</div><p>So a dependency's manifest, which your build loads, can't read your files, write to a table or create atoms.</p>\n<h2 id=\"a-release-imports-what-it-configures\" tabindex=\"-1\">A release imports what it configures <a class=\"header-anchor\" href=\"#a-release-imports-what-it-configures\" aria-label=\"Permalink to &quot;A release imports what it configures&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A release lives in <code v-pre>releases/&lt;name&gt;.bcl</code>. It names the applications to include, the flags for the virtual machine and the system configuration. The section for each application can use that application's <code v-pre>Config</code>, because the release imports the manifest module like any other module:</p>\n<div class=\"language-bcl vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bcl</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% releases/shop.bcl</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Release</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Manifest</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">release</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Release</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    applications</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: [</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    vm</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">VM</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">System</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">get_env</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"NODE_NAME\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"shop@127.0.0.1\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        async_threads</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">64</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    sys_config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        kernel</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: { </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">logger_level</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:warning</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Manifest</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Config</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> { </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">port</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">80</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><p>The release sets only the port. Unification with <code v-pre>Config</code> supplies the other fields from their defaults, and the toolchain writes the result as <code v-pre>sys.config</code>:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">[</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">  {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">kernel</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, [</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">logger_level</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">warning</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">  ]},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">  {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, [</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">port</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">80</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">timeout_ms</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">10000</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">pool_size</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">10</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">  ]}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">].</span></span></code></pre>\n</div><p>It writes the <code v-pre>vm</code> section as <code v-pre>vm.args</code>:</p>\n<div class=\"language-text vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">text</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>-name shop@127.0.0.1</span></span>\n<span class=\"line\"><span>+A 64</span></span></code></pre>\n</div><p>Give the port as <code v-pre>70000</code>, and the release does not compile. The compiler sees that no value satisfies both the release and the type:</p>\n<div class=\"language-text vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">text</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>this intersection: `Config &#x26; {port: 70000, ...Any}` denotes no value: no value is a member of every conjunct</span></span></code></pre>\n</div><p>The team that writes the release and the team that writes <code v-pre>shop</code> can be different. The type is the contract between them, and the compiler checks it.</p>\n<h2 id=\"environments-patch-the-release\" tabindex=\"-1\">Environments patch the release <a class=\"header-anchor\" href=\"#environments-patch-the-release\" aria-label=\"Permalink to &quot;Environments patch the release&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Values that change between environments live in <code v-pre>config/</code>. <code v-pre>config/base.bcl</code> patches every environment, and <code v-pre>config/&lt;env&gt;.bcl</code> patches one:</p>\n<div class=\"language-bcl vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bcl</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% config/prod.bcl</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">release</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    vm</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">System</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">get_env</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"NODE_NAME\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"shop@10.0.0.5\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    sys_config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        kernel</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: { </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">logger_level</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:warning</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: { </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">pool_size</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">50</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><p><code v-pre>bondy config generate --env prod</code> applies <code v-pre>base.bcl</code> and then <code v-pre>prod.bcl</code> to the release. A patch overrides values. It does not unify with them, because with <code v-pre>&amp;</code> a production value that differs from the release's would denote no value. Maps merge, and the patch replaces any other value.</p>\n<p>Two consequences follow. The patch applies to the release's finished values, so the patched <code v-pre>pool_size</code> of 50 leaves <code v-pre>timeout_ms</code> at 10000. The default that reads <code v-pre>pool_size</code> does not run again. Also, the toolchain does not check a patch against <code v-pre>Config</code> when it generates the files. A patch that sets the port to 70000 produces a <code v-pre>sys.config</code> with port 70000.</p>\n<p>The application can check its application environment when it starts:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">App</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> implements</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Application</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">State</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Null</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Manifest</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.{</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> State</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Null</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> start</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_start_type</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Application</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">StartType</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_args</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Any</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Application</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">StartResult</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    let</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Application</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">validate_env</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Shop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Sup</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> stop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_state</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">State</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Atom</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">    :ok</span></span></code></pre>\n</div><p><code v-pre>Application.validate_env/2</code> unifies the application environment with <code v-pre>Config</code> and adds the defaults. With port 70000, it returns an error. The match on <code v-pre>Ok(config)</code> fails, <code v-pre>start/2</code> crashes, and <code v-pre>shop</code> does not start.</p>\n<h2 id=\"one-type-four-readers\" tabindex=\"-1\">One type, four readers <a class=\"header-anchor\" href=\"#one-type-four-readers\" aria-label=\"Permalink to &quot;One type, four readers&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Follow <code v-pre>Config</code> through the project. The manifest declares it, and the <code v-pre>.app.src</code> holds its defaults. The application's code imports it from <code v-pre>Shop.Manifest</code>. A release imports it from the same module, and the compiler checks the release against it. At start, the application checks its application environment against it again.</p>\n<p>In an Erlang project, these are four places that each hold their own copy of the facts. In a Bondy project, they read one type.</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p><a href=\"./2026-10-12-the-type-that-validates.html\">The type that validates</a> explains shapes and unification in Bondy code. <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p><a href=\"./2026-10-12-what-a-function-is-allowed-to-do.html\">What a function is allowed to do</a> explains how the compiler works out what a function can do. <a href=\"#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
            "url": "https://bondy.io/posts/2026-10-12-the-manifest-is-a-module",
            "title": "The manifest is a module",
            "date_modified": "2026-10-12T00:00:00.000Z",
            "author": {
                "name": "Alejandro M. Ramallo"
            },
            "tags": [
                "Language",
                "Configuration",
                "Tooling",
                "Shapes"
            ]
        },
        {
            "id": "https://bondy.io/posts/2026-10-12-the-alarm-that-cleared-itself",
            "content_html": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>A temperature sensor, a threshold, and an alarm. First attempt:</p>\n<div class=\"language-elixir vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">elixir</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">def</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> alarm_active?</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(reading), </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">do:</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> reading </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 100.0</span></span></code></pre>\n</div><p>It is wrong in a way that takes a week to notice. The reading crosses a hundred, the alarm goes active, an operator looks up, the reading dips to ninety-nine, and the alarm is gone. Nobody acknowledged it. Nothing recorded that it happened. Real alarms latch: they stay active from the crossing until someone clears them, whatever the reading does in between.</p>\n<p>Second attempt, the one everyone ships:</p>\n<div class=\"language-elixir vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">elixir</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">def</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_info</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:reading</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, r}, %{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">alarm:</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> false</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> s) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> r </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 100.0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">  do:</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:noreply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, %{s </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">|</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> alarm:</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">def</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_info</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:reading</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, _r}, s),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">  do:</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:noreply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, s}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">def</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:ack</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">_from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, s),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">  do:</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, %{s </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">|</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> alarm:</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> false</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}}</span></span></code></pre>\n</div><p>Now it latches. And now three questions have no answer. Since when has the alarm been active? What crossed the threshold, and what was the reading? Was it active at 03:14 last Tuesday, when the incident report says the pump failed?</p>\n<p>The boolean holds the truth and throws away the time, the cause, and the history. You then spend the next day reconstructing them from log lines, if the log lines are there.</p>\n<p>The <a href=\"./2026-10-12-one-noun-more-roles.html\">second post</a> named the two ideas this needs: a role, the fluent, and a noun, the signal. This post shows what they look like in the design, and says plainly which parts exist.</p>\n<h2 id=\"fluents-what-is-true-now-and-since-when\" tabindex=\"-1\">Fluents: what is true now, and since when <a class=\"header-anchor\" href=\"#fluents-what-is-true-now-and-since-when\" aria-label=\"Permalink to &quot;Fluents: what is true now, and since when&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <a href=\"/glossary/fluent.html\">fluent</a> is a statement whose truth changes only when a named event changes it, and which stays true or false in between. It is the latched alarm, with the time and the cause kept. In the design, a fluent is a constraint with an attribute, and the rules that change it use two built-in operations:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Design. Not in the compiler yet; see the roadmap.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> AlarmActive</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">fluent</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> AlarmActive</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Crossed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">      =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reading</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Float</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Acknowledged</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> latch</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Crossed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">s</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reading</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: _) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> c</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> c</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> initiate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">AlarmActive</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">s</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> clear</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Acknowledged</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">s</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> a</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> a</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> terminate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">AlarmActive</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">s</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p>Read it against the Elixir. <code v-pre>Crossed</code> is the occurrence: it is told to the store, a rule removes it, and the rule's body does not <em>set</em> anything. It <strong>initiates</strong> the fluent, which records that <code v-pre>AlarmActive(sensor: s)</code> holds from now, because of this crossing. <code v-pre>Acknowledged</code> is the other occurrence, and its rule <strong>terminates</strong> the fluent, which records that it stopped holding now, because of this acknowledgement.</p>\n<p>Between the two, the fluent stays true on its own. A reading of ninety-nine is not an occurrence that terminates anything, so it changes nothing. That is the behaviour the first attempt got wrong and the second attempt got right by accident.</p>\n<p>The queries are where the three lost answers come back:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Design.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">holds</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">AlarmActive</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"t1\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))          </span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% is it active now?</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">initiated_at</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">AlarmActive</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"t1\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))   </span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% since when?</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">holds_at</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">AlarmActive</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"t1\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)    </span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% was it active at t?</span></span></code></pre>\n</div><p><code v-pre>holds</code> is the boolean you had. <code v-pre>initiated_at</code> is the timestamp you did not. <code v-pre>holds_at</code> is the historical query that the incident report needed, and it works because the design keeps terminated fluents in a history rather than deleting them.</p>\n<p>Time here is logical, not wall-clock: a counter inside a process, and a hybrid logical clock between processes. That is a deliberate choice, and it is what keeps the fluent store deterministic without processes having to coordinate. &quot;Expires in thirty seconds&quot; becomes a checkpoint computed when the fluent starts, not a comparison against <code v-pre>os:timestamp()</code> in a guard. A guard must not read the clock for the same reason, as the <a href=\"./2026-10-12-what-a-function-is-allowed-to-do.html\">post on effects</a> explained.</p>\n<h2 id=\"signals-what-value-does-this-always-equal\" tabindex=\"-1\">Signals: what value does this always equal <a class=\"header-anchor\" href=\"#signals-what-value-does-this-always-equal\" aria-label=\"Permalink to &quot;Signals: what value does this always equal&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The reading itself is the other idea. A temperature isn't a fact that holds or an event that happens. It's a value that is always current, comes from outside, and remembers nothing. That is a <a href=\"/glossary/signal.html\">signal</a>, and the compiler already accepts its declaration, though nothing runs behind it yet:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Design. The declarations compile; the engine is not built.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reading</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">celsius</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Float</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">signal</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> temperature</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> :=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> latest</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Reading</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"t1\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">signal</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> too_hot</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">     :=</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> temperature</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 100.0</span></span></code></pre>\n</div><p><code v-pre>too_hot</code> is the first attempt's boolean, and it is correct <em>as a signal</em>: true exactly when the reading is high, false the moment it is not, no inertia. The bug was never that expression. The bug was using it as the alarm. The crossing is the instant <code v-pre>too_hot</code> goes from false to true. In the design, that transition is what gets told to the store as <code v-pre>Crossed</code>, and there the fluent takes over.</p>\n<p>So the whole scenario, with every noun and role in its place. The reading is a signal, and the threshold test is a signal over it. The crossing is an occurrence, told to the store. A rewrite rule matches it and initiates the fluent. An acknowledgement is another occurrence, and another rule terminates the fluent. Four nouns and roles, none of them standing in for another, and none of the three questions lost.</p>\n<h2 id=\"what-ships-what-doesn-t\" tabindex=\"-1\">What ships, what doesn't <a class=\"header-anchor\" href=\"#what-ships-what-doesn-t\" aria-label=\"Permalink to &quot;What ships, what doesn't&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Everything shown as working in the earlier posts compiles on the current toolchain, and I have been careful to say so each time. Here is the same care in the other direction.</p>\n<p>The <code v-pre>signal name := expr</code> declaration compiles. There is no signal engine behind it: nothing recomputes, nothing is sampled. The <code v-pre>@fluent</code> attribute, <code v-pre>initiate</code>, <code v-pre>terminate</code>, <code v-pre>holds</code>, <code v-pre>holds_at</code> and the history log do not exist in the compiler. What they need is an extra step in the reactor: after each round of rules, update the fluents and recompute the signals. That step is designed and planned for version 0.9.</p>\n<p>I am publishing the design now, before the code, for the reason the second post gave. These two are the ones whose absence you feel least, until you try to write the alarm. Everyone's first alarm clears itself. Everyone's second alarm forgets why it fired. The language that has both fluents and signals is the one where the third attempt is the first.</p>\n<p>The next post is the last in the series, and it is the whole rate limiter in one place.</p>\n",
            "url": "https://bondy.io/posts/2026-10-12-the-alarm-that-cleared-itself",
            "title": "The alarm that cleared itself",
            "date_modified": "2026-10-12T00:00:00.000Z",
            "author": {
                "name": "Alejandro M. Ramallo"
            },
            "tags": [
                "Language",
                "Fluents",
                "Signals"
            ]
        },
        {
            "id": "https://bondy.io/posts/2026-10-12-what-a-function-is-allowed-to-do",
            "content_html": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>Here is an Erlang spec:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">spec</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> price</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Order </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> order</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> integer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">().</span></span></code></pre>\n</div><p>It tells you what goes in and what comes out. It does not tell you whether <code v-pre>price/1</code> reads a config file or calls a pricing service over the network. Nor does it say whether it writes to an ETS cache, sends a message to a logger, or asks the clock what day it is. All of those are invisible in the signature. All of them change what you can safely do with it: call it twice, cache its result, run it on another node, or call it from inside a rule.</p>\n<p>Erlang handles this for guards by limiting them to a fixed list of built-in functions, since the runtime can't know whether anything else has side effects. You can write <code v-pre>when is_integer(X), X &gt; 0</code> but not <code v-pre>when valid_price(X)</code>. It's safe, and it means any non-trivial condition moves out of the guard and into the body.</p>\n<p>Bondy keeps the safety and drops the restriction. The compiler works out what every function may do, and a guard can call any function it can show is harmless.</p>\n<h2 id=\"capabilities\" tabindex=\"-1\">Capabilities <a class=\"header-anchor\" href=\"#capabilities\" aria-label=\"Permalink to &quot;Capabilities&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <strong><a href=\"/glossary/capability.html\">capability</a></strong> is something a function may do beyond computing its result. The kinds include the file system, the network, ETS tables, the process dictionary, processes and their messages, the clock and random numbers. The compiler infers, for every function, the set of capabilities its body uses, including everything it calls. You write no annotations for this.</p>\n<p>That set is part of the function's type. A function type in Bondy is not only &quot;takes <code v-pre>Int</code>, returns <code v-pre>Int</code>&quot; but &quot;takes <code v-pre>Int</code>, returns <code v-pre>Int</code>, and may do these things&quot;. Keeping it on the type, rather than in a side table, is what makes the next section work.</p>\n<p>A function whose set is empty does nothing but compute. The compiler describes functions with a small vocabulary of <a href=\"/glossary/law.html\">laws</a>:</p>\n<ul>\n<li><strong>pure</strong>: it changes nothing outside itself, and the only outside things it reads are the clock and random numbers. Writing, sending, spawning and reading an ETS table all make a function impure.</li>\n<li><strong>deterministic</strong>: the same input always gives the same output. No clock, no randomness, no outside state.</li>\n<li><strong>total</strong>: it always returns.</li>\n<li><strong>idempotent</strong>: applying it twice gives the same result as applying it once, <code v-pre>f(f(x)) == f(x)</code>. Normalising data is the typical case, and so is unifying a value with a fixed shape: doing it again adds nothing.</li>\n<li><strong>monotone</strong>: giving it more information never gives you less back. This is what lets the engine run a function over data that is still arriving.</li>\n</ul>\n<figure class=\"laws-diagram\">\n<svg viewBox=\"0 0 560 290\" role=\"img\" aria-labelledby=\"laws-title laws-desc\" style=\"max-width:560px;width:100%;height:auto;color:currentColor;font-family:inherit\">\n<title id=\"laws-title\">How Bondy's function laws relate</title>\n<desc id=\"laws-desc\">monotone and idempotent each include deterministic, which includes pure. total is independent of the others.</desc>\n<defs><marker id=\"laws-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0,0 L10,5 L0,10 z\" fill=\"currentColor\"/></marker></defs>\n<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n<rect x=\"20\" y=\"20\" width=\"170\" height=\"44\" rx=\"10\"/>\n<rect x=\"250\" y=\"20\" width=\"170\" height=\"44\" rx=\"10\"/>\n<rect x=\"135\" y=\"122\" width=\"170\" height=\"44\" rx=\"10\"/>\n<rect x=\"135\" y=\"224\" width=\"170\" height=\"44\" rx=\"10\"/>\n<rect x=\"370\" y=\"224\" width=\"170\" height=\"44\" rx=\"10\" stroke-dasharray=\"5 4\"/>\n</g>\n<g stroke=\"currentColor\" stroke-width=\"1.5\" marker-end=\"url(#laws-arrow)\">\n<line x1=\"105\" y1=\"64\" x2=\"205\" y2=\"120\"/>\n<line x1=\"335\" y1=\"64\" x2=\"235\" y2=\"120\"/>\n<line x1=\"220\" y1=\"166\" x2=\"220\" y2=\"222\"/>\n</g>\n<g fill=\"currentColor\" font-size=\"16\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"ui-monospace,SFMono-Regular,Menlo,monospace\">\n<text x=\"105\" y=\"42\">monotone</text>\n<text x=\"335\" y=\"42\">idempotent</text>\n<text x=\"220\" y=\"144\">deterministic</text>\n<text x=\"220\" y=\"246\">pure</text>\n<text x=\"455\" y=\"246\">total</text>\n</g>\n<g fill=\"currentColor\" font-size=\"12\" opacity=\"0.7\">\n<text x=\"232\" y=\"198\">includes</text>\n<text x=\"455\" y=\"206\" text-anchor=\"middle\">independent</text>\n</g>\n</svg>\n<figcaption>Each law includes the ones below it: a monotone or idempotent function is also deterministic, and a deterministic function is also pure. <code>total</code> is separate.</figcaption>\n</figure>\n<p><code v-pre>idempotent</code> and <code v-pre>monotone</code> both include <code v-pre>deterministic</code>, which includes <code v-pre>pure</code>. The compiler checks these two only for functions whose body it can analyse, such as a one-argument function that unifies its argument with a fixed shape. For anything else, you state them as a hint (below).</p>\n<h2 id=\"stating-a-law-and-having-it-checked\" tabindex=\"-1\">Stating a law, and having it checked <a class=\"header-anchor\" href=\"#stating-a-law-and-having-it-checked\" aria-label=\"Permalink to &quot;Stating a law, and having it checked&quot;\">&ZeroWidthSpace;</a></h2>\n<p>You can write a law down with <code v-pre>@law</code>, and the compiler checks it:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@law</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:pure</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:deterministic</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> double</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">x</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    x</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> *</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 2</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@law</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:pure</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> shout</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">msg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> String</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    IO</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">println</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">msg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    msg</span></span></code></pre>\n</div><p><code v-pre>double</code> compiles. <code v-pre>shout</code> does not:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>Function shout/1 declares @law(:pure) </span></span>\n<span class=\"line\"><span>but the analysis cannot prove it: its inferred capability set </span></span>\n<span class=\"line\"><span>is not empty. This is not a proof that the law is false: the </span></span>\n<span class=\"line\"><span>analysis is sufficient, not complete. Restructure the function into </span></span>\n<span class=\"line\"><span>the fragment the compiler recognises, or carry the law as </span></span>\n<span class=\"line\"><span>@law(pure: :hint) to assert it untested</span></span></code></pre>\n</div><p>The message is careful about what it knows. The analysis never calls something pure that is not, but it may fail to prove that something is pure when it is. When that happens and you know better, <code v-pre>@law(pure: :hint)</code> records the claim without the check, and the difference between checked and trusted stays visible in the code.</p>\n<p>Reading the clock shows the difference between the first two laws. This function is pure, because it only reads the clock, but it is not deterministic:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@law</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:pure</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> now_ms</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Time</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">system_time</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()</span></span></code></pre>\n</div><p>That compiles. Declare it <code v-pre>@law(:deterministic)</code> instead and the compiler refuses: &quot;its inferred capability set admits nondeterminism&quot;.</p>\n<h2 id=\"guards\" tabindex=\"-1\">Guards <a class=\"header-anchor\" href=\"#guards\" aria-label=\"Permalink to &quot;Guards&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Back to the rules post. A rule's <code v-pre>when</code> guard runs while the engine decides which rule fires. If a guard wrote to a log, the log would depend on the order the engine tried rules in. If it read the clock, the same facts could fire different rules on different runs. So a guard can call your own functions, and the compiler requires them to be deterministic and to always return:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reading</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Alarm</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">        =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> too_hot</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bool</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    v</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 100</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> alarm</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Reading</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">s</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> r</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        r</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">        too_hot</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Alarm</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">sensor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">s</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p><code v-pre>too_hot</code> has no annotations, and the rule compiles, because the compiler infers that <code v-pre>too_hot</code> does nothing but compare. Use a version with a debug print instead:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> too_hot_logged</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bool</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    IO</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">println</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"checking\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    v</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 100</span></span></code></pre>\n</div><p>and the rule is rejected:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>Function too_hot_logged/1 is called </span></span>\n<span class=\"line\"><span>from a CHR rule guard but is not verified as pure and deterministic. </span></span>\n<span class=\"line\"><span>A guard is evaluated during rule matching, where an effect or a </span></span>\n<span class=\"line\"><span>nondeterministic result changes which rules fire; CHR rule guards </span></span>\n<span class=\"line\"><span>require @law(:deterministic) and @law(:total).</span></span></code></pre>\n</div><p>The rule's body, its <code v-pre>tell</code> section, has no such restriction. It runs once the engine has decided, so it is the right place for effects.</p>\n<h2 id=\"pure-if-its-argument-is-pure\" tabindex=\"-1\">Pure if its argument is pure <a class=\"header-anchor\" href=\"#pure-if-its-argument-is-pure\" aria-label=\"Permalink to &quot;Pure if its argument is pure&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Plenty of functions take other functions:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> twice</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">f</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: ((</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">x</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    f</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">f</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">x</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span></code></pre>\n</div><p>Is <code v-pre>twice</code> pure? It depends entirely on <code v-pre>f</code>. A system that summarised each function with a single yes or no would have to answer &quot;no&quot;, to be safe, and every caller of <code v-pre>twice</code> would inherit the &quot;no&quot;. Because Bondy keeps capabilities on the function type, <code v-pre>twice</code>'s type says instead that it does <em>whatever <code v-pre>f</code> does</em>. The answer is settled at each call:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@law</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:pure</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:deterministic</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> quad</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">x</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">    twice</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">fun</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">y</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> y</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> *</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 2</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">x</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span></code></pre>\n</div><p>That compiles, and <code v-pre>quad(3)</code> is <code v-pre>12</code>. Pass <code v-pre>twice</code> a function that prints, and the same <code v-pre>@law</code> line on the caller fails with <code v-pre>law_not_verified</code>. This is what lets functions that take functions be used inside guards and pure code without giving up the checks.</p>\n<h2 id=\"limiting-what-a-function-may-do\" tabindex=\"-1\">Limiting what a function may do <a class=\"header-anchor\" href=\"#limiting-what-a-function-may-do\" aria-label=\"Permalink to &quot;Limiting what a function may do&quot;\">&ZeroWidthSpace;</a></h2>\n<p><code v-pre>@law</code> says what a function does <em>not</em> do. <code v-pre>@capability</code> says the most a function <em>may</em> do, as an upper limit the compiler holds the body to:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">capability</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">kind</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:ets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">mode</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:read_write</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> remember</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">table</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">key</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Table</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">insert</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">table</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, (</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">key</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span></code></pre>\n</div><p>That compiles. Declare the same body as read-only:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">capability</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">kind</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:ets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">mode</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:read</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> sneaky</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">table</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">key</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Table</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">insert</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">table</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, (</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">key</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span></code></pre>\n</div><p>and the body is caught writing:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>Function sneaky/3 declares @capability (ets, Atom, read), but its </span></span>\n<span class=\"line\"><span>body captures (ets, Atom, write), which the declaration does not </span></span>\n<span class=\"line\"><span>cover. Widen the declaration to cover it, or compile with </span></span>\n<span class=\"line\"><span>--laws=trust to assert the narrower declaration.</span></span></code></pre>\n</div><p>The same attribute goes on an <code v-pre>@external</code>, a Bondy binding to an Erlang function, to say what the foreign code does. For Erlang functions the compiler already knows about, such as <code v-pre>file:read_file/1</code>, it checks your declaration against what it knows. For the rest, your declaration is what it trusts.</p>\n<h2 id=\"where-it-is-checked\" tabindex=\"-1\">Where it is checked <a class=\"header-anchor\" href=\"#where-it-is-checked\" aria-label=\"Permalink to &quot;Where it is checked&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The compiler does not check capabilities at every call. Ordinary code calls whatever it likes, as it does in Erlang. The checks sit at the places where an effect would break a promise:</p>\n<ul>\n<li>a function that declares a <code v-pre>@law</code> or a <code v-pre>@capability</code></li>\n<li>a rule guard</li>\n<li>the body of a derivation rule, which must be deterministic for a query to have one answer</li>\n<li>a default or computed field in a shape, which may be evaluated more than once or in any order</li>\n<li>code compiled as untrusted (below)</li>\n</ul>\n<p>Each of these asks the same question: are the capabilities of this function within what is allowed here? For guards and deterministic functions, the allowance is empty. A pure function is also allowed the clock and random numbers.</p>\n<p>Untrusted code is the case this was built for. A compilation unit can be compiled in <strong>safe mode</strong>, with an explicit <strong>allowance</strong>: the capabilities the host grants it, for example &quot;read and write ETS, nothing else&quot;. The compiler rejects a function that needs more. The unit also cannot declare its own laws, because code that is not trusted cannot vouch for itself. It is aimed at plugins, user-supplied logic, and code generated by tools rather than people. Today safe mode is an option of the compiler API, but there is no command-line switch for it yet.</p>\n<h2 id=\"what-is-not-built-yet\" tabindex=\"-1\">What is not built yet <a class=\"header-anchor\" href=\"#what-is-not-built-yet\" aria-label=\"Permalink to &quot;What is not built yet&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The capability kinds today are fairly coarse. A finer-grained ordering, where a grant for &quot;this ETS table, read only&quot; can stand in for a narrower request, is designed but not built. So are capabilities that can be handed from one process to another as values, scoped to a single interaction. I will write about both when they compile.</p>\n<h2 id=\"why-this-design\" tabindex=\"-1\">Why this design <a class=\"header-anchor\" href=\"#why-this-design\" aria-label=\"Permalink to &quot;Why this design&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Two choices shaped it.</p>\n<p><strong>Inferred, not declared.</strong> You can write <code v-pre>@law</code> and <code v-pre>@capability</code>, but you never have to. Ordinary code carries no annotations, and the compiler still knows what every function may do. Annotations appear only where you want a promise enforced.</p>\n<p><strong>Tracked, not handled.</strong> Languages such as Koka treat effects as operations that handlers can intercept and redefine. Bondy does not. It tracks what a function may do and checks it where it matters. That is all the BEAM needs: processes, messages and supervisors already cover what effect handlers are usually used for.</p>\n<p>What you get is a spec that finally includes the part the Erlang spec left out. The rest of the series leans on it. Rules can call your functions, queries always have one answer, and code you did not write can run with only the capabilities you granted it.</p>\n",
            "url": "https://bondy.io/posts/2026-10-12-what-a-function-is-allowed-to-do",
            "title": "What a function is allowed to do",
            "date_modified": "2026-10-12T00:00:00.000Z",
            "author": {
                "name": "Alejandro M. Ramallo"
            },
            "tags": [
                "Language",
                "Effects",
                "Capabilities",
                "Types"
            ]
        },
        {
            "id": "https://bondy.io/posts/2026-10-12-ask-questions-of-your-data",
            "content_html": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>Every codebase has one of these. Who is in Alice's management chain? Which products can substitute for this one, directly or through a chain of substitutes? Which documents can this user reach through the groups they belong to? You write it as a recursive function over a list of pairs:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% reports: [{Who, Boss}]</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Boss, Who, Reports) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    lists</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">member</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({Who, Boss}, Reports)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    orelse</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> lists</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">any</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">        fun</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({W, B}) -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            W </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=:=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Who </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">andalso</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Boss, B, Reports)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">        end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        Reports</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    ).</span></span></code></pre>\n</div><p>It works until the data has a cycle, and then it never returns. So you add a visited set:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Boss, Who, Reports) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">    manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Boss, [Who], Reports, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">sets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(_Boss, [], _Reports, _Seen) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">    false</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Boss, [W | Rest], Reports, Seen) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    case</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> lists</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">member</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({W, Boss}, Reports) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">of</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        false</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            Next </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> [</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">                B ||  {W1, B} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">&#x3C;-</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Reports, W1 </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=:=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> W,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">                        not</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> sets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">is_element</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(B, Seen)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            ],</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">            manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">                Boss, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">                Next </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">++</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Rest, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">                Reports, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">                sets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">add_element</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(W, Seen)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            )</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span></span></code></pre>\n</div><p>Now it terminates. It is a depth-first search with an accumulator, written by hand. Yet the question it answers is one sentence long: <em>B manages W if W reports to B, or W reports to someone B manages.</em> The search order, the cycle protection and the accumulator are not part of the question. They are the price of asking it in a language whose only tool for &quot;what follows from these facts&quot; is a function that walks them.</p>\n<h2 id=\"facts-you-look-up\" tabindex=\"-1\">Facts you look up <a class=\"header-anchor\" href=\"#facts-you-look-up\" aria-label=\"Permalink to &quot;Facts you look up&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The <a href=\"./2026-10-12-one-noun-more-roles.html\">second post</a> called this the <a href=\"/glossary/relation.html\">relation</a> role. A relation holds facts, and you can ask about them in either direction: who Alice reports to, or who reports to Carol. Here it is, and every line compiles:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> ReportsTo</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">fact</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"alice\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"bob\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">fact</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"bob\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"carol\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">mid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">mid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span></code></pre>\n</div><p><code v-pre>relation ReportsTo = {...}</code> declares what a fact looks like and makes it a relation. A <code v-pre>fact</code> declaration is static data written into the module: the facts the relation starts with. The next section shows how facts come and go while the program runs.</p>\n<p>The two <code v-pre>Manages</code> lines are a <strong>derivation rule</strong>, the second kind of <a href=\"/glossary/rule.html\">rule</a> in Bondy. Read <code v-pre>:-</code> as &quot;if&quot;. Bob manages Alice if Alice reports to Bob. Carol manages Alice if Alice reports to someone Carol manages. Two rules for the same relation mean &quot;either one&quot;: <code v-pre>Manages</code> holds whenever either rule says so.</p>\n<p>Then you ask:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> team_of</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    query</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">w</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> bosses_of</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    query</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">b</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">end</span></span></code></pre>\n</div><p><code v-pre>team_of</code> returns every <code v-pre>w</code> the rules can reach. A field with a value is a filter, and a field left as a variable is what you want back. <code v-pre>bosses_of</code> asks the same relation the opposite question, everyone above a person, with no second traversal. With the two facts above:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>team_of(\"carol\") => [{\"alice\"}, {\"bob\"}]</span></span>\n<span class=\"line\"><span>bosses_of(\"alice\") => [{\"bob\"}, {\"carol\"}]</span></span></code></pre>\n</div><p>Each answer is a tuple with one element per variable you left open.</p>\n<h2 id=\"where-the-facts-live\" tabindex=\"-1\">Where the facts live <a class=\"header-anchor\" href=\"#where-the-facts-live\" aria-label=\"Permalink to &quot;Where the facts live&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A relation says what a fact looks like. It says nothing about where facts are kept. That is the job of a <strong>relation store</strong>: a supervised process that owns the storage for a set of relations. You declare one as a module implementing the <code v-pre>Relation.Store</code> behaviour, with four required callbacks:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Org</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Store</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> implements</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Relation</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Store</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Org</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.{</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Atom</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">    :org</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> adapter</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Module</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Relation</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Adapter</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Table</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> opts</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Map</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    #{}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> relations</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> List</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">TypeRef</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    [</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]</span></span></code></pre>\n</div><p><code v-pre>adapter()</code> picks the backend. <code v-pre>Relation.Adapter.Table</code> keeps the facts in ETS, in memory. <code v-pre>Postgres.Relation.Adapter</code> keeps them in PostgreSQL tables, with options such as the schema to use and whether to create the tables on start:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> adapter</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Module</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Postgres</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Relation</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Adapter</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> opts</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Map</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        :schema</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\"> \"public\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        :ddl</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> :drop_and_create</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        :on_destroy</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> :drop</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    }</span></span></code></pre>\n</div><p>Moving between the two changes the store module and nothing else. The relations, the rules and the queries stay the same, because they never mentioned storage in the first place. You start a store with <code v-pre>Relation.Store.start(Org.Store)</code>, which runs it under Bondy's own supervisor, or put <code v-pre>Relation.Store.start_link/1</code> in your supervision tree. When it starts, it creates the storage and loads the relation's <code v-pre>fact</code> declarations into it.</p>\n<p>From then on, facts come and go at runtime. You look up the relation's storage and insert or delete rows, each row built with the relation's constructor:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">let</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reports</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Relation</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Registry</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">lookup</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Relation</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Adapter</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Table</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">insert</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    reports</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"dee\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"carol\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% => Ok(:inserted)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Relation</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Adapter</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Table</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">delete</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    reports</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"alice\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"bob\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% => Ok({:deleted, ReportsTo(who: \"alice\", boss: \"bob\")})</span></span></code></pre>\n</div><p><code v-pre>insert</code> refuses a row of the wrong relation with <code v-pre>Error(:wrong_relation)</code>, and <code v-pre>delete</code> returns <code v-pre>Error(:not_found)</code> for a row that is not there. The next <code v-pre>query</code> sees the change, derived relations included, because a query evaluates the rules over whatever the store holds at that moment. After the two calls above, <code v-pre>team_of(&quot;carol&quot;)</code> includes Dee, and Alice is no longer anyone's report.</p>\n<p>There is no update. A fact is a statement that something is true. When it stops being true you delete it, and when something new becomes true you insert that. Changing Alice's manager is a delete of the old fact and an insert of the new one.</p>\n<h2 id=\"why-it-terminates\" tabindex=\"-1\">Why it terminates <a class=\"header-anchor\" href=\"#why-it-terminates\" aria-label=\"Permalink to &quot;Why it terminates&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The recursive <code v-pre>Manages</code> has no visited set because it does not need one. The engine evaluates derivation rules in rounds: start with the facts, apply every rule, add whatever is new, and repeat until a round adds nothing. <code v-pre>Manages</code> only recombines names that are already in the facts, so the rounds run out. A cycle in the data produces facts that already exist, so it adds nothing. Each round also only looks at what changed in the round before, so the engine doesn't redo work.</p>\n<p>A rule that computes a new value in its body, such as <code v-pre>n = m + 1</code>, is different: it can add a new fact in every round. The compiler accepts it, and the engine stops it at a round limit with a <code v-pre>fixpoint_diverged</code> error.</p>\n<p>That is what <code v-pre>Seen</code> did in the Erlang version, except you don't have to remember to write it. And the first Erlang version, the one that loops forever, can't be written at all.</p>\n<p>Facts plus &quot;if&quot; rules, repeated until nothing new appears, is <strong><a href=\"/glossary/datalog.html\">Datalog</a></strong>. It dates from 1977, and a lot of graph and policy engines run on it.</p>\n<h2 id=\"negation-and-the-version-the-compiler-refuses\" tabindex=\"-1\">Negation, and the version the compiler refuses <a class=\"header-anchor\" href=\"#negation-and-the-version-the-compiler-refuses\" aria-label=\"Permalink to &quot;Negation, and the version the compiler refuses&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Sometimes the question is about absence. Who has no manager? Which node has no outgoing edge?</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Node</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Edge</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">to</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Isolated</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Node</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    not</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Edge</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">to</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: _)</span></span></code></pre>\n</div><p><code v-pre>not Edge(...)</code> succeeds when there is no matching fact. This is where &quot;repeat until nothing changes&quot; can go wrong. If <code v-pre>A</code> is defined as &quot;not <code v-pre>B</code>&quot; and <code v-pre>B</code> as &quot;not <code v-pre>A</code>&quot;, each new <code v-pre>A</code> takes away a reason for a <code v-pre>B</code>, and the other way round. So the rounds never settle. Datalog handles this with <a href=\"/glossary/stratification.html\">stratification</a>: a relation has to be fully computed before a rule can ask what is <em>not</em> in it. That excludes any relation that depends on its own absence.</p>\n<p>I wrote the bad case to see what happens:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> A</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Node</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    not</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> B</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> B</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Node</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    not</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> A</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span></code></pre>\n</div><p>The compiler rejects it as <code v-pre>unstratifiable</code> and names the cycle: <code v-pre>['Bondy.Probe.B','Bondy.Probe.A']</code>. It is the relation-side counterpart of the guard check on rewrite rules from <a href=\"./2026-10-12-rules-that-fire-themselves.html\">two posts ago</a>: a program that could not have a well-defined answer does not compile.</p>\n<h2 id=\"an-example-with-three-relations\" tabindex=\"-1\">An example with three relations <a class=\"header-anchor\" href=\"#an-example-with-three-relations\" aria-label=\"Permalink to &quot;An example with three relations&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Access control is where most people meet this, because some part of the policy is always <a href=\"/glossary/transitive.html\">transitive</a>. Users own resources. Admins can reach everything. A manager can reach what their reports own:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> User</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">      =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">role</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Atom</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Resource</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">owner</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> ReportsTo</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> CanAccess</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">user</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">resource</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Resource</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">resource</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">owner</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">user</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> CanAccess</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">user</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">resource</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    User</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">user</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">role</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:admin</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Resource</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">resource</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    ReportsTo</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">mid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">mid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> CanAccess</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">user</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">resource</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Manages</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">boss</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">user</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">who</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">owner</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Resource</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">resource</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">owner</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">owner</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span></code></pre>\n</div><p>Five rules, each one a line from the policy document. The function version would be a <code v-pre>can_access/2</code> with three branches, one calling the <code v-pre>manages/3</code> from the top of this post, and next year a fourth branch for delegated access. Here that is one more rule.</p>\n<p>The <code v-pre>CanAccess</code> rules also don't say which one to try first, when to stop, or what to cache. The engine decides that.</p>\n<h2 id=\"two-stores-two-kinds-of-rule\" tabindex=\"-1\">Two stores, two kinds of rule <a class=\"header-anchor\" href=\"#two-stores-two-kinds-of-rule\" aria-label=\"Permalink to &quot;Two stores, two kinds of rule&quot;\">&ZeroWidthSpace;</a></h2>\n<p>We have now seen both kinds of rule, so here they are side by side.</p>\n<p>A <strong>derivation rule</strong> works over relations. It only adds facts, worked out from the ones already there. It never removes anything, so it finishes whenever its rules only recombine values already in the facts (stratification takes care of <code v-pre>not</code>). This is Datalog.</p>\n<p>A <strong>rewrite rule</strong> works over constraints, the facts rules act on. It removes and replaces: a <code v-pre>Bucket</code> with three tokens becomes a <code v-pre>Bucket</code> with two, and the old one is gone. This is CHR. Because it can remove, the compiler has to check what Datalog gets for free, which is what the guard check in the rules post does.</p>\n<p>Both match facts the same way. A <code v-pre>ReportsTo</code> in a relation and a <code v-pre>Bucket</code> in a constraint store are both values of a structural type. A rule matches either one by <a href=\"/glossary/unification.html\">unification</a>, the operation the <a href=\"./2026-10-12-the-type-that-validates.html\">third post</a> shows in detail. You call neither kind of rule: both fire on their own. They do live in different stores, on purpose. Facts you look up and facts rules act on behave differently, so keeping them apart lets the compiler check each kind for what can go wrong with it.</p>\n<p>That covers the roles that ship, and both kinds of rule. Before the roles that are still in design, one question the series has kept deferring: how does the compiler know a guard is pure? That is the next post.</p>\n",
            "url": "https://bondy.io/posts/2026-10-12-ask-questions-of-your-data",
            "title": "Ask questions of your data",
            "date_modified": "2026-10-12T00:00:00.000Z",
            "author": {
                "name": "Alejandro M. Ramallo"
            },
            "tags": [
                "Language",
                "Relations",
                "Datalog"
            ]
        },
        {
            "id": "https://bondy.io/posts/2026-10-12-the-process-that-owns-the-store",
            "content_html": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>The rules in the last post had no process around them. That was deliberate, because the rules are the new part. But a store needs an owner, and on the BEAM the owner is a process.</p>\n<p>If you set out to write the limiter as a proper state machine on the BEAM, you would reach for <code v-pre>gen_statem</code>. It is OTP's behaviour for a process with named states and a clause for each event in each state. Bondy has it too, as <code v-pre>GenStatem</code>: the same behaviour, the same callbacks, with types. So that is the baseline for this post, written in Bondy.</p>\n<h2 id=\"the-limiter-as-a-genstatem\" tabindex=\"-1\">The limiter as a GenStatem <a class=\"header-anchor\" href=\"#the-limiter-as-a-genstatem\" aria-label=\"Permalink to &quot;The limiter as a GenStatem&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Here is the limiter built the OTP way: one process per bucket, a supervisor to start them, and a registry (<code v-pre>global</code>) to find each bucket. The two Erlang tabs have the same module written with <code v-pre>gen_statem</code>, to compare line by line. One uses <code v-pre>handle_event_function</code> mode, as the Bondy version does. The other has a function per state (<code v-pre>state_functions</code>), the style an Erlang developer would more often reach for.</p>\n<div class=\"vp-code-group vp-adaptive-theme\"><div class=\"tabs\"><input type=\"radio\" name=\"group-19ITg\" id=\"tab-GaYaprp\" checked><label data-title=\"Bondy\" for=\"tab-GaYaprp\">Bondy</label><input type=\"radio\" name=\"group-19ITg\" id=\"tab-dk99o5J\" ><label data-title=\"Erlang · handle_event_function\" for=\"tab-dk99o5J\">Erlang · handle_event_function</label><input type=\"radio\" name=\"group-19ITg\" id=\"tab-yPg098c\" ><label data-title=\"Erlang · state_functions\" for=\"tab-yPg098c\">Erlang · state_functions</label></div><div class=\"blocks\">\n<div class=\"language-bondy vp-adaptive-theme active\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> implements</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenStatem</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">State</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Atom</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Data</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@moduledoc</span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\"> \"\"\"</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">A token bucket rate limiter.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">\"\"\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Kernel</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Gen</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.{</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">InitOk</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">NextStateActions</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">KeepStateActions</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> BucketSup</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Tokens</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Refilled</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">new_count</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- API ------------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    GenStatem</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), __MODULE__, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, [])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> create</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    DynamicSupervisor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_child</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        BucketSup</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            :id</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            :start</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> (__MODULE__, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, [</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    )</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">    call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">n</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">    call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, (</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">n</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    (</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:via</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:global</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, (__MODULE__, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">msg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    case</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> rescue</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">fun</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenStatem</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">msg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">of</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">            reply</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Exception</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">class</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:exit</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reason</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: (</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:noproc</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, _))) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">            Error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:no_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">e</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">            exit</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">e</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.reason)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- Callbacks -------------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> callback_mode</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">    :handle_event_function</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> init</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    InitOk</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">((</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.tokens </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">==</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    NextStateActions</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        :empty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.max), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        [(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))]</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    )</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">((</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    let</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.tokens </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    KeepStateActions</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.max), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        [(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))]</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    )</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">((</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), (</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">n</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    let</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> min</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.tokens </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">+</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> n</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.max)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    KeepStateActions</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.max), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        [(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">new_count</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))]</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    )</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">((</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:empty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    (</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:keep_state_and_data</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, [(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">((</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), (</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">n</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:empty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">when</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> n</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    let</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> min</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">n</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.max)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    NextStateActions</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        :available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">d</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.max), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        [(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">new_count</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))]</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    )</span></span></code></pre>\n</div><div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">).</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">moduledoc</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\"> \"\"\"</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">A token bucket rate limiter.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"\"\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">behaviour</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">gen_statem</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">export</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">create</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">2</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">2</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]).</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">export</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">2</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">init</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">callback_mode</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">4</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- API ------------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, Max) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    gen_statem</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">via</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">global</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, {</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">MODULE</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Id}}, </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">MODULE</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Max, []).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% One process per bucket, started under bucket_sup and found by id.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">create</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, Max) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    supervisor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_child</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">bucket_sup</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, [Id, Max]).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">    call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, N) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">    call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, N}).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, Msg) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    try</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> gen_statem</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">via</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">global</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, {</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">MODULE</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Id}}, Msg)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    catch</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> exit</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">noproc</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">_</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">} -> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">no_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- Callbacks -------------------------------------</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">callback_mode</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">    handle_event_function</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">init</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Max) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Max, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">max</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Max}}.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- State Management-------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From}, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> D) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">next_state</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">empty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, D#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}, [Reply]};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From}, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> D) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">keep_state</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, D#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}, [Reply]};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From}, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, N}, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> M} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> D) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    T1 </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> min</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">+</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> N, M),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, T1}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">keep_state</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, D#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T1}, [Reply]};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From}, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">empty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, _D) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">keep_state_and_data</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, [Reply]};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From}, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, N}, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">empty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> M} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> D) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> N </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    T1 </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> min</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(N, M),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, T1}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">next_state</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, D#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T1}, [Reply]}.</span></span></code></pre>\n</div><div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">).</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">moduledoc</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\"> \"\"\"</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">A token bucket rate limiter.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"\"\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">behaviour</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">gen_statem</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">export</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">create</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">2</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">2</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]).</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">export</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">2</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">init</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">callback_mode</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">3</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">empty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">3</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- API ------------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, Max) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    gen_statem</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">via</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">global</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, {</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">MODULE</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Id}}, </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">MODULE</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Max, []).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% One process per bucket, started under bucket_sup and found by id.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">create</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, Max) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    supervisor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_child</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">bucket_sup</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, [Id, Max]).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">    call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, N) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">    call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, N}).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, Msg) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    try</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> gen_statem</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">via</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">global</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, {</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">MODULE</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Id}}, Msg)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    catch</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> exit</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">noproc</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">_</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">} -> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">no_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- Callbacks -------------------------------------</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">callback_mode</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">    state_functions</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">init</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Max) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Max, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">max</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Max}}.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- State Management-------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From}, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> D) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">next_state</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">empty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, D#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}, [Reply]};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From}, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> D) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">keep_state</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, D#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}, [Reply]};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From}, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, N}, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> M} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> D) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    T1 </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> min</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">+</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> N, M),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, T1}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">keep_state</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, D#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T1}, [Reply]}.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">empty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From}, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, _D) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">keep_state_and_data</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, [Reply]};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">empty</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From}, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, N}, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> M} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> D) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> N </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    T1 </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> min</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(N, M),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, From, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, T1}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">next_state</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, D#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T1}, [Reply]}.</span></span></code></pre>\n</div></div></div>\n<p>If you know gen_statem, everything is where you expect it. <code v-pre>GenStatem.start_link</code> and <code v-pre>GenStatem.call</code> are <code v-pre>gen_statem:start_link</code> and <code v-pre>gen_statem:call</code>, and the callbacks are <code v-pre>callback_mode/0</code>, <code v-pre>init/1</code> and <code v-pre>handle_event/4</code>. A <code v-pre>(:via, :global, …)</code> name registers the process, and <code v-pre>DynamicSupervisor</code> starts one bucket per id. What Bondy adds is types. The data is a <code v-pre>Tokens</code> struct, the replies are typed values, and each transition is a typed constructor such as <code v-pre>NextStateActions</code>. Bondy has no <code v-pre>try</code>/<code v-pre>catch</code>, so <code v-pre>rescue</code> turns the exit from a bucket that does not exist into <code v-pre>Error(:no_bucket)</code> and lets any other exit through.</p>\n<p>Run it: create a bucket with two tokens, request three times, refill by five, request once more, and then request a bucket that does not exist:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>[</span></span>\n<span class=\"line\"><span>    Ok(&#x3C;0.206.0>),</span></span>\n<span class=\"line\"><span>    Allowed(1),</span></span>\n<span class=\"line\"><span>    Allowed(0),</span></span>\n<span class=\"line\"><span>    :denied,</span></span>\n<span class=\"line\"><span>    Refilled(2),</span></span>\n<span class=\"line\"><span>    Allowed(1),</span></span>\n<span class=\"line\"><span>    Error(:no_bucket)</span></span>\n<span class=\"line\"><span>]</span></span></code></pre>\n</div><p>For one bucket this reads well. Its whole life is visible at a glance: <code v-pre>available</code> until it runs out, then <code v-pre>empty</code>, denying every request until a refill moves it back. That is what a state machine is for: one thing, a handful of named modes, and a clear answer for each event in each mode. Think of a connection that is connecting, connected or closing, or a door that is open, closed or locked.</p>\n<p>This version runs one process per bucket, so it needs a supervisor to start them and a registry to find them. That is a choice about deployment, not a property of GenStatem: one GenStatem could as well hold every bucket in its data. For two states and two events, a GenStatem is hard to beat.</p>\n<p>The difference shows when the machine grows, and real limiters do. Premium buckets that are never denied. A daily spending cap on top of the token count. A bucket an operator has suspended. A throttled state after repeated denials.</p>\n<p>In a GenStatem each of these is a new state or a flag in the data. A new state needs a clause for every event it can receive, and every clause has to check a flag. The premium override has to appear in every state that can deny, and the cap has to appear in every clause that can allow. The logic is organised by state, so one sentence about the system gets copied into each state it applies to.</p>\n<p>Then there is what you do when something worth knowing happens inside. It is a log call with the details flattened into a string, a callback module passed in at start, or a <code v-pre>pg</code> group you publish to. Three mechanisms for &quot;something happened&quot;, none of them typed, each tied to a particular listener.</p>\n<p>None of the OTP part is wasted. The process is the right unit. A store has an owner, the owner has a mailbox, the mailbox handles one message at a time, and the supervisor restarts the owner when it dies. Erlang got this right in 1986 and Bondy keeps it: every store is owned by one OTP process and settles inside that process. What changes is where the logic lives.</p>\n<h2 id=\"what-implements-checks\" tabindex=\"-1\">What <code v-pre>implements</code> checks <a class=\"header-anchor\" href=\"#what-implements-checks\" aria-label=\"Permalink to &quot;What `implements` checks&quot;\">&ZeroWidthSpace;</a></h2>\n<p>One line of the code above deserves a closer look before moving on: the first. In Erlang, <code v-pre>-behaviour(gen_statem)</code> is a promise the compiler barely checks. Omit <code v-pre>init/1</code> and <code v-pre>erlc</code> prints a warning and builds the module anyway. Return <code v-pre>&quot;denied&quot;</code> from a state function and it compiles cleanly, then crashes the state machine the first time that clause runs. (Both checked with OTP 29.)</p>\n<p>In Bondy, <code v-pre>implements</code> is checked the way a type is. A behaviour is an ordinary module whose contract functions are marked <code v-pre>@callback</code>, each with a full type, plus abstract types: placeholders the behaviour leaves to whoever implements it. <code v-pre>GenStatem</code> has three, <code v-pre>State</code>, <code v-pre>Data</code> and <code v-pre>Event</code>. The <code v-pre>implements</code> line binds them for this module: <code v-pre>GenStatem(State = Atom, Data = Tokens)</code>. <code v-pre>Data</code> and <code v-pre>Event</code> default to <code v-pre>Any</code> if left out. <code v-pre>State</code> has no default, so leaving it out is an error, and so is binding a name the behaviour does not declare.</p>\n<p>The compiler then substitutes those bindings into every callback's type and checks the module's functions against the result:</p>\n<ul>\n<li>Every required callback must be there. <code v-pre>handle_event/4</code> missing is an error, not a warning. The optional ones (<code v-pre>terminate/3</code>, <code v-pre>code_change/4</code>, <code v-pre>format_status/1</code>) can be left out, as they are here.<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\">[1]</a></sup></li>\n<li>Each function must accept at least what the callback can be given, and return no more than the callback allows. A parameter can be wider than the contract but never narrower, which is why <code v-pre>init</code> takes its argument unannotated: <code v-pre>GenStatem.start_link</code> can pass anything there, so <code v-pre>init(max: Int)</code> is rejected. A result can be narrower than the contract but never wider.</li>\n</ul>\n<p>Because <code v-pre>Data = Tokens</code>, &quot;no more than the callback allows&quot; means every transition in every clause must carry a <code v-pre>Tokens</code>. Write one clause that returns a plain map as the new data, and the module does not compile:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>-- TYPE CHECK FAILED ---------------------------------</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>I ran into a problem in your code:</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>type checking failed with 1 error</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>  --> lib/bucket.bondy:57:1</span></span>\n<span class=\"line\"><span>   |</span></span>\n<span class=\"line\"><span>57 | public function handle_event((:call, from), :request, :available, d: Tokens) </span></span>\n<span class=\"line\"><span>   | ^ callback type mismatch for `handle_event/4`; expected: ((Atom | {Atom, {Pid, Any}}, Any, Atom, Bondy.Bucket.Tokens) :: Atom | {Atom, List(Any)} | Bondy.Kernel.Gen.KeepState | Bondy.Kernel.Gen.KeepStateActions | Bondy.Kernel.Gen.NextState | Bondy.Kernel.Gen.NextStateActions | Bondy.Kernel.Gen.Stop | Bondy.Kernel.Gen.StopAndReply); found: ((Any, Any, Any, Any) :: {keep_state_and_data, NonEmptyList({reply, {type_var,44,0,meta}, denied})} | Bondy.Kernel.Gen.KeepStateActions | Bondy.Kernel.Gen.NextStateActions); required by GenStatem</span></span></code></pre>\n</div><p>That is the benefit. A behaviour stops being a list of function names you hope you spelled right and becomes a contract the compiler holds you to. The mistake surfaces when you build, as an error pointing at the callback, not as a crash on whichever event first reaches the wrong clause. The <code v-pre>implements</code> line also documents the process: anyone reading it knows what its state is and what data it holds, before reading a single clause. And it is the same mechanism for every behaviour: <code v-pre>GenServer</code>, <code v-pre>Supervisor</code>, <code v-pre>GenReactor</code> below, and any behaviour you write yourself, an ordinary module with <code v-pre>@callback</code> functions.</p>\n<h2 id=\"genreactor-a-rule-based-approach\" tabindex=\"-1\">GenReactor - a rule based approach <a class=\"header-anchor\" href=\"#genreactor-a-rule-based-approach\" aria-label=\"Permalink to &quot;GenReactor - a rule based approach&quot;\">&ZeroWidthSpace;</a></h2>\n<p><code v-pre>GenReactor</code> is Bondy's behaviour for a process whose state is a store of facts and whose logic is rules. Here is the same limiter as a GenReactor, every bucket in one process. It compiles on the current toolchain:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> RateLimiter</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> implements</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Event</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> | </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> | </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@moduledoc</span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\"> \"\"\"</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">A token bucket rate limiter.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">\"\"\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.{</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">NoReply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Request</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Refill</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">amount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Denied</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">   =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Refilled</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">new_count</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- Rules--- ----------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> consume_token</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> deny_when_empty</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> refill_within_cap</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">amount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">amt</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> +</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> amt</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =&#x3C;</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> m</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> +</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> amt</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">new_count</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> +</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> amt</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> refill_to_cap</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">amount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">amt</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> +</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> amt</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> m</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">new_count</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- API ------------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(__MODULE__, [], [])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(__MODULE__, [], #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:name</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">})</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> create_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">tell</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">tell</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">amount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">tell</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">amount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">amount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> stop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">stop</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">reactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% --- Callbacks -------------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> init</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_args</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_ctx</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)  </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_ctx</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)   </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_ctx</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">v</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(_, _)                  </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    NoReply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()</span></span></code></pre>\n</div><p>Read it top to bottom: three constraints, three events, four rules that connect them, then an API and two callbacks. The API is the interesting part, because of how little it does.</p>\n<p><code v-pre>implements GenReactor(Event = Allowed | Denied | Refilled)</code> says what this process can say to the outside world. The union is the process's public vocabulary, and it is typed. Nothing else leaves.</p>\n<p><code v-pre>init/1</code> returns the initial store, here empty. A store starts as a list of constraints, so a limiter with pre-provisioned buckets would return them here.</p>\n<p><code v-pre>handle_event/2</code> is the one decision the process makes that the rules do not: which events the caller gets to hear. <code v-pre>Reply(value: v)</code> sends the event back to whoever added the fact that caused it. <code v-pre>NoReply()</code> drops it. Omit the catch-all clause and an event nobody handles crashes the process. That is the right default for a typed vocabulary: an event you forgot about is a bug, not something to drop quietly.</p>\n<p>The API is one verb: <strong>tell</strong>, which means adding a fact to the store. <code v-pre>GenReactor.tell/2</code> is the synchronous form: it waits until the rules have settled and the resulting event has come back. <code v-pre>GenReactor.tell_request/2</code> tells the store a constraint and returns a request id at once, so you decide when to fetch the response. The one other verb is <strong>ask</strong>: <code v-pre>GenReactor.ask</code> queries the store without adding anything to it. There is no <code v-pre>handle_call</code>, because there is nothing to dispatch on. A fact arrives, the rules do what they do, the events come out.</p>\n<p>Run it with the same steps as the GenStatem above: create a bucket with two tokens, request three times, then refill by five.</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>[Ok(Allowed(id: \"k\", remaining: 1)), Ok(Allowed(id: \"k\", remaining: 0)),</span></span>\n<span class=\"line\"><span> Ok(Denied(\"k\")), Ok(Refilled(id: \"k\", new_count: 2))]</span></span></code></pre>\n</div><p>The refill lands on <code v-pre>refill_to_cap</code>, because five more tokens would overflow a bucket that holds two.</p>\n<h2 id=\"the-event-role\" tabindex=\"-1\">The event role <a class=\"header-anchor\" href=\"#the-event-role\" aria-label=\"Permalink to &quot;The event role&quot;\">&ZeroWidthSpace;</a></h2>\n<p>This is where the <a href=\"/glossary/event.html\">event</a> role from the <a href=\"./2026-10-12-one-noun-more-roles.html\">second post</a> stops being a definition and becomes a mechanism.</p>\n<p>A constraint stays in the store until a rule removes it. An event is declared with <code v-pre>event</code> instead of <code v-pre>constraint</code>, and the difference is one thing: every time the rules settle, the events are swept out of the store. <code v-pre>Allowed</code> never piles up. It exists for as long as <code v-pre>handle_event</code> needs to see it, and then it is gone. That is what &quot;it happened&quot; means at runtime: a moment, not a state.</p>\n<p>That makes the event the answer to the three mechanisms for &quot;something happened&quot; from the top of this post. It is typed, so the listener gets <code v-pre>Allowed(id: &quot;api-key-1&quot;, remaining: 2)</code>, not a log line. It is addressed to nobody in particular, so the same event can go back to the caller, go to a subscriber, or be ignored. The rule that emitted it neither knows nor cares. And it is checked like every other value, so the same type checker that checks a <code v-pre>Bucket</code> in a rule checks an <code v-pre>Allowed</code> in a <code v-pre>handle_event</code> clause.</p>\n<p>The rule emits. The process decides who hears. The sweep keeps the two apart.</p>\n<h2 id=\"side-by-side-comparison\" tabindex=\"-1\">Side by side comparison <a class=\"header-anchor\" href=\"#side-by-side-comparison\" aria-label=\"Permalink to &quot;Side by side comparison&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The two versions organise the logic differently.</p>\n<p>In the GenStatem, the logic is organised by state: what <code v-pre>available</code> does with each event, what <code v-pre>empty</code> does with each event. That is the right shape when there are a few states and each event means something clear in each of them. The number of clauses grows roughly as states times events, which is nothing at two by two.</p>\n<p>In the GenReactor, the logic is organised by sentence. The state is a set of facts, and a rule fires when a combination of facts matches it: this request, with that bucket, when it has tokens. &quot;Empty&quot; is not a state to keep in sync: it is a bucket with zero tokens, and a rule that cares says so in its pattern. A new requirement is a new rule next to the others. The premium flag from the <a href=\"./2026-10-12-rules-that-fire-themselves.html\">previous post</a> is one rule, not a clause added to every state that can deny. The rules grow with the number of things you need to say, not with states times events.</p>\n<p>Everything else is the same for both. Each is a supervised OTP process. Each can run one per bucket, with a supervisor and a registry, or one for many buckets. A GenReactor holding every bucket is the same choice as a GenStatem holding a map of them. Either way you shard by id when a single mailbox becomes the bottleneck.</p>\n<table tabindex=\"0\">\n<thead>\n<tr>\n<th></th>\n<th>GenServer</th>\n<th>GenStatem</th>\n<th>GenReactor</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>State</strong></td>\n<td>any term you manage</td>\n<td>one named state, plus data</td>\n<td>a store of facts</td>\n</tr>\n<tr>\n<td><strong>Input</strong></td>\n<td>call, cast, info</td>\n<td>call, cast, info, timeouts, per state</td>\n<td>tell (and ask, to query the store)</td>\n</tr>\n<tr>\n<td><strong>Logic</strong></td>\n<td>callback clauses</td>\n<td>clauses per state and event</td>\n<td>rules that fire when facts combine</td>\n</tr>\n<tr>\n<td><strong>Output</strong></td>\n<td>replies and side effects</td>\n<td>replies and actions</td>\n<td>typed events, routed by <code v-pre>handle_event</code></td>\n</tr>\n<tr>\n<td><strong>Fits</strong></td>\n<td>a service with some state</td>\n<td>a few states, each event clear in each</td>\n<td>a machine that keeps growing, with rules that combine conditions</td>\n</tr>\n</tbody>\n</table>\n<p>None of them replaces the others, and Bondy has all three: a door is still best written as a state machine. Use a GenReactor when the question is not &quot;what mode am I in?&quot; but &quot;what follows from everything I know?&quot;.</p>\n<h2 id=\"the-process-you-already-know\" tabindex=\"-1\">The process you already know <a class=\"header-anchor\" href=\"#the-process-you-already-know\" aria-label=\"Permalink to &quot;The process you already know&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Underneath, this is an OTP process. <code v-pre>start_link</code> returns a pid, the named form registers it, and it goes into a supervisor's child list like any other worker. When it crashes, the supervisor restarts it, <code v-pre>init</code> rebuilds the store, and the facts it had collected are gone. That is the same durability contract a GenStatem's data has, for the same reason. If a store must survive a restart, its facts go somewhere durable and <code v-pre>init</code> reloads them, which is what you do today.</p>\n<p>It compiles to a standard <code v-pre>.beam</code> module named <code v-pre>Bondy.RateLimiter</code>, so you can call it from Erlang:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Pid} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> 'Bondy.RateLimiter'</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">start_link</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">'Bondy.RateLimiter'</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">create_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Pid, &#x3C;&#x3C;</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"api-key-1\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">>>, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">3</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">'Bondy.RateLimiter'</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Pid, &#x3C;&#x3C;</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"api-key-1\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">>>).</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% => {ok,{'Bondy.RateLimiter.Allowed',&#x3C;&#x3C;\"api-key-1\">>,2}}</span></span></code></pre>\n</div><p>The reply is <code v-pre>{ok, Event}</code>, with the event as a tagged tuple, because that is how a Bondy value built from a structural type looks at runtime.</p>\n<p>Distribution is OTP distribution. Register the reactor globally, tell it something from another node, and the constraint crosses the wire like any other message. There is no Bondy-specific transport in this release. The <a href=\"https://github.com/lasp-lang/partisan\" target=\"_blank\" rel=\"noreferrer\">Partisan</a> layer that runs Bondy Connect in production is the next step, not something the compiler does today.</p>\n<h2 id=\"what-is-left-of-the-genstatem\" tabindex=\"-1\">What is left of the GenStatem <a class=\"header-anchor\" href=\"#what-is-left-of-the-genstatem\" aria-label=\"Permalink to &quot;What is left of the GenStatem&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Look back at the GenStatem at the top. The process around the logic is the same: a supervisor, and a registry if you run one process per bucket. What changed is the inside. The clauses per state and event are gone, replaced by rules, and the rules are the part that grows when the system does. Each new requirement is one more sentence, not a clause in every state it touches. The logging calls, callback modules or <code v-pre>pg</code> groups you would add for &quot;something happened&quot; are replaced by a typed event union and one function that decides who hears.</p>\n<p>Plain messages that are not facts, such as a socket's data, still have a place: a GenReactor can define an optional <code v-pre>handle_info</code>.</p>\n<p>What you have is a supervised process whose state is a set of facts and whose logic is a set of sentences about those facts. That is the noun the BEAM already gives you, the process, doing the job it always did, with the state machine inside it written in the language that was missing.</p>\n<p>The store in this post holds constraints, and rules rewrite them. The next post is about the other kind of store: one that holds facts you look up rather than facts you impose. It also covers the other kind of rule, the one that runs over it.</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>This is why the limiter uses <code v-pre>:handle_event_function</code>, one callback for every state, rather than <code v-pre>:state_functions</code>, one function per state, which an Erlang version would often use. Bondy accepts <code v-pre>:state_functions</code> at runtime, but its conformance check does not follow it yet. It still requires <code v-pre>handle_event/4</code>, so the module needs a placeholder that is never called. And it does not check the state functions themselves, because their names are the module's own rather than the behaviour's. A state function that returns a string compiles, and fails at runtime with <code v-pre>bad_return_from_state_function</code>, exactly as in Erlang. With <code v-pre>:handle_event_function</code>, every transition passes through the one callback the compiler checks. <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
            "url": "https://bondy.io/posts/2026-10-12-the-process-that-owns-the-store",
            "title": "The process that owns the store",
            "summary": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>The rules in the last post had no process around them. That was deliberate, because the rules are the new part. But a store needs an owner, and on the BEAM the owner is a process.</p>\n<p>If you set out to write the limiter as a proper state machine on the BEAM, you would reach for <code v-pre>gen_statem</code>. It is OTP's behaviour for a process with named states and a clause for each event in each state. Bondy has it too, as <code v-pre>GenStatem</code>: the same behaviour, the same callbacks, with types. So that is the baseline for this post, written in Bondy.</p>\n<h2 id=\"the-limiter-as-a-genstatem\" tabindex=\"-1\">The limiter as a GenStatem <a class=\"header-anchor\" href=\"#the-limiter-as-a-genstatem\" aria-label=\"Permalink to &quot;The limiter as a GenStatem&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Here is the limiter built the OTP way: one process per bucket, a supervisor to start them, and a registry (<code v-pre>global</code>) to find each bucket. The two Erlang tabs have the same module written with <code v-pre>gen_statem</code>, to compare line by line. One uses <code v-pre>handle_event_function</code> mode, as the Bondy version does. The other has a function per state (<code v-pre>state_functions</code>), the style an Erlang developer would more often reach for.</p>\n<div class=\"vp-code-group vp-adaptive-theme\"><div class=\"tabs\"><input type=\"radio\" name=\"group-m0TVS\" id=\"tab-JJNbVae\" checked><label data-title=\"Bondy\" for=\"tab-JJNbVae\">Bondy</label></div><div class=\"blocks\">\n<div class=\"language-bondy vp-adaptive-theme active\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> implements</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenStatem</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">State</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Atom</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Data</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@moduledoc</span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\"> \"\"\"</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">A token bucket rate limiter.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">\"\"\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Kernel</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Gen</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.{</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">InitOk</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">NextStateActions</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">KeepStateActions</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> BucketSup</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Tokens</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Refilled</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">new_count</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%%</span></span></code></pre>\n</div></div></div>\n",
            "date_modified": "2026-10-12T00:00:00.000Z",
            "author": {
                "name": "Alejandro M. Ramallo"
            },
            "tags": [
                "Language",
                "Events",
                "Rules",
                "OTP"
            ]
        },
        {
            "id": "https://bondy.io/posts/2026-10-12-rules-that-fire-themselves",
            "content_html": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>A token bucket rate limiter, as a gen_server. You have written this one, or its cousin:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">handle_call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Id}, _From, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">buckets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Bs} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> State0) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    case</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> maps</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">find</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, Bs) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">of</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B0} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            B </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B0#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            State </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> State0#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">buckets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Bs#{Id :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Reply, State};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}} -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, State0};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">no_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}, State0}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">handle_call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Id, Amount}, _From, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">buckets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Bs} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> State0) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    case</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> maps</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">find</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, Bs) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">of</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> M} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B0} -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            T1 </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> min</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">+</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Amount, M),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            B </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B0#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T1},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, T1},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            State </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> State0#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">buckets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Bs#{Id :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Reply, State};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">no_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}, State0}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span></span></code></pre>\n</div><p>It is short, idiomatic, and it works, but where is the rule? &quot;A request against a bucket that has tokens uses one up and is allowed&quot; is one sentence. In the code it is spread across the message shape, the map lookup, the guard, the bucket update, the state update and the reply tuple. Add a second rule, &quot;premium buckets are never denied&quot;, and count the clauses you have to touch. The logic is scattered because the language has no place for a sentence of the form <em>when these things are together, do this to them</em>. It only has functions, so the sentence dissolves into control flow.</p>\n<h2 id=\"a-store-and-rules-over-it\" tabindex=\"-1\">A store, and rules over it <a class=\"header-anchor\" href=\"#a-store-and-rules-over-it\" aria-label=\"Permalink to &quot;A store, and rules over it&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The <a href=\"./2026-10-12-one-noun-more-roles.html\">second post</a> said a rule runs when the world makes it true. Here is what that means in practice. The same limiter, in Bondy:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Request</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Denied</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> consume_token</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> deny_when_empty</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p>The first two lines declare two structural types and put them in the <a href=\"/glossary/constraint.html\">constraint</a> role. So their values live in a <strong>store</strong>: a collection of facts the rules work on. The next two are <a href=\"/glossary/event.html\">events</a>, which leave the store as soon as the rules are done with them.</p>\n<p>Then come two <a href=\"/glossary/rule.html\">rules</a>, and each reads as one sentence. <code v-pre>consume_token</code>: <em>on</em> a <code v-pre>Request</code> and a <code v-pre>Bucket</code> with the same id, <em>remove</em> both <em>when</em> the bucket has tokens, and <em>tell</em> a bucket with one token fewer and an <code v-pre>Allowed</code> event. <code v-pre>deny_when_empty</code>: <em>on</em> a bucket with no tokens and a request made against it, <em>remove</em> the request and <em>tell</em> <code v-pre>Denied</code>.</p>\n<p>Nobody calls a rule. You add <code v-pre>Request(id: &quot;api-key-1&quot;)</code> to the store, and the store looks for rules whose left-hand side now matches, and fires them. It repeats until no rule matches any more. That end state is called the <strong>fixed point</strong>: the store has settled, and the events produced along the way are what the outside world sees.</p>\n<p>Three pieces of syntax carry all of the meaning.</p>\n<p><strong><code v-pre>on</code>, <code v-pre>as</code> and <code v-pre>remove</code></strong> say what happens to the facts a rule matched. <code v-pre>on</code> lists the patterns, <code v-pre>as</code> names a matched fact, and <code v-pre>remove</code> lists the named facts the rule takes out of the store. Every fact it does not remove stays. The bucket in <code v-pre>deny_when_empty</code> is kept, because denying a request does not change the bucket. The bucket in <code v-pre>consume_token</code> is removed, because a new one with a different count replaces it.</p>\n<p>There is no update, only remove and add: <code v-pre>tell</code> adds facts. The compiler knows from its declaration whether each is a constraint, which stays in the store, or an event, which leaves once the store settles. This split between kept and removed facts comes from <a href=\"https://dtai.cs.kuleuven.be/projects/CHR/about.shtml\" target=\"_blank\" rel=\"noreferrer\">Constraint Handling Rules</a> (CHR), a rule language from the logic programming world that Bondy builds on.</p>\n<p><strong>A variable that appears in two patterns is a join.</strong> <code v-pre>rid</code> appears in both <code v-pre>Request</code> and <code v-pre>Bucket</code>, so the rule only fires for a request and a bucket with the same id. This is the part a function cannot give you, because a function receives one input. A rule matches a <em>combination</em> of facts, and the engine finds the combination for you. In the Erlang version the join is <code v-pre>maps:find(Id, Bs)</code>, but here it is a shared name. Matching a pattern like <code v-pre>Bucket(id: rid, tokens: 0)</code> against a fact in the store is the same <a href=\"/glossary/unification.html\">unification</a> the <a href=\"./2026-10-12-the-type-that-validates.html\">previous post</a> showed on values.</p>\n<p><strong><code v-pre>when</code> is a guard, and it must be pure.</strong> <code v-pre>t &gt; 0</code> is an ordinary boolean expression over the matched variables, and it can call functions. The compiler checks that every function a guard calls has no side effects, always gives the same answer for the same input, and always returns. Otherwise it rejects the rule. That keeps the choice of which rule fires a question about the store, never about the clock or the network. How the compiler knows what a function does is the subject of the <a href=\"./2026-10-12-what-a-function-is-allowed-to-do.html\">post on effects</a>.</p>\n<h2 id=\"adding-the-second-rule\" tabindex=\"-1\">Adding the second rule <a class=\"header-anchor\" href=\"#adding-the-second-rule\" aria-label=\"Permalink to &quot;Adding the second rule&quot;\">&ZeroWidthSpace;</a></h2>\n<p>&quot;Premium buckets are never denied.&quot; In the gen_server this touches the <code v-pre>{ok, #{tokens := 0}}</code> clause, adds a field to the bucket map, and changes the guard on the first clause. In the store, it is one more field and one more rule:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    premium</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bool</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> false</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@priority</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">10</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> premium_always_allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">premium</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p>The field defaults to <code v-pre>false</code>, so the existing rules, which build a <code v-pre>Bucket</code> without it, stay as they are. Without annotations, the engine tries rules in the order they are written. <code v-pre>@priority(10)</code> moves this rule ahead of the others, so a premium request never reaches <code v-pre>deny_when_empty</code>, wherever the rule sits in the file. What matters is that the new requirement arrived as one new rule next to the old ones, and nothing else changed.</p>\n<h2 id=\"rules-that-look-at-the-whole-store\" tabindex=\"-1\">Rules that look at the whole store <a class=\"header-anchor\" href=\"#rules-that-look-at-the-whole-store\" aria-label=\"Permalink to &quot;Rules that look at the whole store&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A guard can also ask about the store as a whole. Say every allowed request leaves a <code v-pre>Spent</code> fact behind, and a bucket is exhausted once there are as many of them as its cap:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Spent</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cost</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Exhausted</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">   =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> exhausted</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cap</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">        count_if</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Spent</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">>=</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> cap</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Exhausted</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p><code v-pre>count_if</code> counts every <code v-pre>Spent</code> in the store that matches the pattern. There are a few of these aggregates, among them <code v-pre>count_if</code>, <code v-pre>exists</code>, <code v-pre>maxover</code> and <code v-pre>findall</code>. They let a rule see the state of the world, not only the facts it matched. The gen_server equivalent is a fold over a map inside a guard, which means a helper function you write, name and test separately.</p>\n<h2 id=\"what-the-compiler-refuses\" tabindex=\"-1\">What the compiler refuses <a class=\"header-anchor\" href=\"#what-the-compiler-refuses\" aria-label=\"Permalink to &quot;What the compiler refuses&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A rule is a declaration, not a block of control flow, so the compiler can inspect it before it runs. My first version of a spending cap was the obvious one: allow a request while fewer requests than the cap have been spent.</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> allow_under_cap</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cap</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">        count_if</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Spent</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">&#x3C;</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> cap</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Spent</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p>The compiler refused it:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>-- TYPE CHECK FAILED ---------------------------------</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>I ran into a problem in your code:</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>type checking failed with 1 error</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>Guard of rule allow_under_cap is not monotone under store growth (verdict: anti_monotone), which would break confluence. Absence tests and anti-monotone comparators are not supported; encode the absence as a complementary constraint</span></span></code></pre>\n</div><p>Here is what it means. Facts can arrive in any order: a request, then another request, then a refill, from different callers at the same time. A rule set is well behaved (the CHR term is <em>confluent</em>) when the store reaches the same state whatever that order was. A guard that can go from true to false as more facts arrive breaks this.</p>\n<p><code v-pre>count_if(…) &lt; cap</code> is true while few facts have been spent and becomes false as more arrive, so whether a request is allowed would depend on who got in first. So the compiler requires that once a guard is true, adding facts can't make it false. <code v-pre>count_if(P) &gt;= k</code> passes, because more facts only keep it true. <code v-pre>count_if(P) &lt; k</code> and <code v-pre>count_if(P) == 0</code> don't.</p>\n<p>&quot;Encode the absence as a complementary constraint&quot; is the fix, and you have already seen it. Instead of counting what has been spent and comparing against a cap, keep a count of what is <em>left</em> in the store and let rules change it. That is the <code v-pre>tokens</code> field of <code v-pre>Bucket</code>, and <code v-pre>consume_token</code> is the rule that uses it. The compiler pushed me back to the design that was right from the start.</p>\n<p>The compiler does not yet prove that a rule set terminates<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\">[1]</a></sup>. The guard check is the part of that story that ships today, and I would rather say so than have you discover it. It also catches the common mistakes, with errors that name the rule. They are a <code v-pre>remove</code> of a name that no pattern binds with <code v-pre>as</code>, a name bound or removed twice, and a section written twice or out of order. The order is always <code v-pre>on</code>, <code v-pre>remove</code>, <code v-pre>when</code>, <code v-pre>tell</code>, <code v-pre>end</code>.</p>\n<h2 id=\"what-it-replaces\" tabindex=\"-1\">What it replaces <a class=\"header-anchor\" href=\"#what-it-replaces\" aria-label=\"Permalink to &quot;What it replaces&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The <code v-pre>handle_call</code> at the top of this post is a rule engine written by hand. It has a store (the state map), a set of rules (the clauses), a matcher (the patterns and guards), and an update (the map syntax). Every gen_server whose state is a collection of things that interact is one of these, and in every one of them the rules are dissolved into the plumbing.</p>\n<p>A rule store gives you the sentences back. The bucket, the request, the cap and the premium flag are facts. &quot;A request with tokens available is allowed&quot; is a rule. The engine does the matching, the ordering and the settling. What is left to write is the part of the gen_server that was never about the rules: who can add facts to the store, and who hears what comes out. That is a process, and it is the next post.</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>It is work in progress and part of the roadmap. <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
            "url": "https://bondy.io/posts/2026-10-12-rules-that-fire-themselves",
            "title": "Rules that fire themselves",
            "summary": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>A token bucket rate limiter, as a gen_server. You have written this one, or its cousin:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">handle_call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Id}, _From, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">buckets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Bs} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> State0) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    case</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> maps</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">find</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, Bs) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">of</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B0} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            B </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B0#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            State </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> State0#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">buckets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Bs#{Id :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Reply, State};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}} -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, State0};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">no_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}, State0}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">handle_call</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refill</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Id, Amount}, _From, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">buckets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Bs} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> State0) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    case</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> maps</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">find</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Id, Bs) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">of</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">max</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> M} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B0} -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            T1 </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> min</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(T </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">+</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Amount, M),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            B </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B0#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> T1},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            Reply </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refilled</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, T1},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            State </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> State0#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">buckets</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Bs#{Id :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> B}},</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Reply, State};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">            {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, {</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">no_bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}, State0}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.</span></span></code></pre>\n</div><p>It is short, idiomatic, and it works, but where is the rule? &quot;A request against a bucket that has tokens uses one up and is allowed&quot; is one sentence. In the code it is spread across the message shape, the map lookup, the guard, the bucket update, the state update and the reply tuple. Add a second rule, &quot;premium buckets are never denied&quot;, and count the clauses you have to touch. The logic is scattered because the language has no place for a sentence of the form <em>when these things are together, do this to them</em>. It only has functions, so the sentence dissolves into control flow.</p>\n<h2 id=\"a-store-and-rules-over-it\" tabindex=\"-1\">A store, and rules over it <a class=\"header-anchor\" href=\"#a-store-and-rules-over-it\" aria-label=\"Permalink to &quot;A store, and rules over it&quot;\">&ZeroWidthSpace;</a></h2>\n<p>The <a href=\"./2026-10-12-one-noun-more-roles.html\">second post</a> said a rule runs when the world makes it true. Here is what that means in practice. The same limiter, in Bondy:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Request</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Denied</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> consume_token</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> deny_when_empty</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p>The first two lines declare two structural types and put them in the <a href=\"/glossary/constraint.html\">constraint</a> role. So their values live in a <strong>store</strong>: a collection of facts the rules work on. The next two are <a href=\"/glossary/event.html\">events</a>, which leave the store as soon as the rules are done with them.</p>\n<p>Then come two <a href=\"/glossary/rule.html\">rules</a>, and each reads as one sentence. <code v-pre>consume_token</code>: <em>on</em> a <code v-pre>Request</code> and a <code v-pre>Bucket</code> with the same id, <em>remove</em> both <em>when</em> the bucket has tokens, and <em>tell</em> a bucket with one token fewer and an <code v-pre>Allowed</code> event. <code v-pre>deny_when_empty</code>: <em>on</em> a bucket with no tokens and a request made against it, <em>remove</em> the request and <em>tell</em> <code v-pre>Denied</code>.</p>\n<p>Nobody calls a rule. You add <code v-pre>Request(id: &quot;api-key-1&quot;)</code> to the store, and the store looks for rules whose left-hand side now matches, and fires them. It repeats until no rule matches any more. That end state is called the <strong>fixed point</strong>: the store has settled, and the events produced along the way are what the outside world sees.</p>\n<p>Three pieces of syntax carry all of the meaning.</p>\n<p><strong><code v-pre>on</code>, <code v-pre>as</code> and <code v-pre>remove</code></strong> say what happens to the facts a rule matched. <code v-pre>on</code> lists the patterns, <code v-pre>as</code> names a matched fact, and <code v-pre>remove</code> lists the named facts the rule takes out of the store. Every fact it does not remove stays. The bucket in <code v-pre>deny_when_empty</code> is kept, because denying a request does not change the bucket. The bucket in <code v-pre>consume_token</code> is removed, because a new one with a different count replaces it.</p>\n<p>There is no update, only remove and add: <code v-pre>tell</code> adds facts. The compiler knows from its declaration whether each is a constraint, which stays in the store, or an event, which leaves once the store settles. This split between kept and removed facts comes from <a href=\"https://dtai.cs.kuleuven.be/projects/CHR/about.shtml\" target=\"_blank\" rel=\"noreferrer\">Constraint Handling Rules</a> (CHR), a rule language from the logic programming world that Bondy builds on.</p>\n<p><strong>A variable that appears in two patterns is a join.</strong> <code v-pre>rid</code> appears in both <code v-pre>Request</code> and <code v-pre>Bucket</code>, so the rule only fires for a request and a bucket with the same id. This is the part a function cannot give you, because a function receives one input. A rule matches a <em>combination</em> of facts, and the engine finds the combination for you. In the Erlang version the join is <code v-pre>maps:find(Id, Bs)</code>, but here it is a shared name. Matching a pattern like <code v-pre>Bucket(id: rid, tokens: 0)</code> against a fact in the store is the same <a href=\"/glossary/unification.html\">unification</a> the <a href=\"./2026-10-12-the-type-that-validates.html\">previous post</a> showed on values.</p>\n<p><strong><code v-pre>when</code> is a guard, and it must be pure.</strong> <code v-pre>t &gt; 0</code> is an ordinary boolean expression over the matched variables, and it can call functions. The compiler checks that every function a guard calls has no side effects, always gives the same answer for the same input, and always returns. Otherwise it rejects the rule. That keeps the choice of which rule fires a question about the store, never about the clock or the network. How the compiler knows what a function does is the subject of the <a href=\"./2026-10-12-what-a-function-is-allowed-to-do.html\">post on effects</a>.</p>\n<h2 id=\"adding-the-second-rule\" tabindex=\"-1\">Adding the second rule <a class=\"header-anchor\" href=\"#adding-the-second-rule\" aria-label=\"Permalink to &quot;Adding the second rule&quot;\">&ZeroWidthSpace;</a></h2>\n<p>&quot;Premium buckets are never denied.&quot; In the gen_server this touches the <code v-pre>{ok, #{tokens := 0}}</code> clause, adds a field to the bucket map, and changes the guard on the first clause. In the store, it is one more field and one more rule:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    premium</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bool</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> false</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@priority</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">10</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> premium_always_allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">premium</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p>The field defaults to <code v-pre>false</code>, so the existing rules, which build a <code v-pre>Bucket</code> without it, stay as they are. Without annotations, the engine tries rules in the order they are written. <code v-pre>@priority(10)</code> moves this rule ahead of the others, so a premium request never reaches <code v-pre>deny_when_empty</code>, wherever the rule sits in the file. What matters is that the new requirement arrived as one new rule next to the old ones, and nothing else changed.</p>\n<h2 id=\"rules-that-look-at-the-whole-store\" tabindex=\"-1\">Rules that look at the whole store <a class=\"header-anchor\" href=\"#rules-that-look-at-the-whole-store\" aria-label=\"Permalink to &quot;Rules that look at the whole store&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A guard can also ask about the store as a whole. Say every allowed request leaves a <code v-pre>Spent</code> fact behind, and a bucket is exhausted once there are as many of them as its cap:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Spent</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cost</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Exhausted</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">   =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> exhausted</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cap</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">        count_if</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Spent</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">>=</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> cap</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Exhausted</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p><code v-pre>count_if</code> counts every <code v-pre>Spent</code> in the store that matches the pattern. There are a few of these aggregates, among them <code v-pre>count_if</code>, <code v-pre>exists</code>, <code v-pre>maxover</code> and <code v-pre>findall</code>. They let a rule see the state of the world, not only the facts it matched. The gen_server equivalent is a fold over a map inside a guard, which means a helper function you write, name and test separately.</p>\n<h2 id=\"what-the-compiler-refuses\" tabindex=\"-1\">What the compiler refuses <a class=\"header-anchor\" href=\"#what-the-compiler-refuses\" aria-label=\"Permalink to &quot;What the compiler refuses&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A rule is a declaration, not a block of control flow, so the compiler can inspect it before it runs. My first version of a spending cap was the obvious one: allow a request while fewer requests than the cap have been spent.</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> allow_under_cap</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cap</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">        req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">        count_if</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Spent</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">&#x3C;</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> cap</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Spent</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">        Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p>The compiler refused it:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>-- TYPE CHECK FAILED</span></span></code></pre>\n</div>",
            "date_modified": "2026-10-12T00:00:00.000Z",
            "author": {
                "name": "Alejandro M. Ramallo"
            },
            "tags": [
                "Language",
                "Rules",
                "OTP"
            ]
        },
        {
            "id": "https://bondy.io/posts/2026-10-12-the-type-that-validates",
            "content_html": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>Data validation is one of the most boring and error-prone (yet critical) jobs we do. It happens at the edges of our systems, where data arrives from a request, a config file or another service. It has to happen before the data means anything. Once the data is inside, code can trust it and not check it again.</p>\n<p>Here is how that usually looks for a config record in Erlang:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">    max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> pos_integer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">    refill_rate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> pos_integer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">spec</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">map</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">().</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Max, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refill_rate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Rate} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> M)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  when</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> is_integer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Max), Max </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Max </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=&#x3C;</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10000</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">       is_integer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Rate), Rate </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    M;</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">_</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">    error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">badarg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">).</span></span></code></pre>\n</div><p>Count the places the same fact lives. The <code v-pre>-type</code> says <code v-pre>max_tokens</code> is a positive integer, for Dialyzer<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\">[1]</a></sup>. The guard says it again, for the runtime. The ceiling of <code v-pre>10000</code> appears only in the guard, because the type spec cannot express it. So the type is now a rough summary of the guard rather than the truth.</p>\n<p>And the whole thing covers one way of building a config. Add optional fields, or a config that arrives in pieces, and you write another function with another copy of the rules.</p>\n<p>The second post showed the other half of the problem: rules that relate two fields, like &quot;a discount must not exceed a tenth of the total&quot;. They don't fit in any schema DSL, so they become control flow. This post covers both halves.</p>\n<h2 id=\"shapes\" tabindex=\"-1\">Shapes <a class=\"header-anchor\" href=\"#shapes\" aria-label=\"Permalink to &quot;Shapes&quot;\">&ZeroWidthSpace;</a></h2>\n<p>In Bondy, a <a href=\"/glossary/shape.html\">shape</a> is a value that describes which values are allowed. These are all shapes:</p>\n<table tabindex=\"0\">\n<thead>\n<tr>\n<th>Expression</th>\n<th>Meaning</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code v-pre>Int</code></td>\n<td>an integer</td>\n</tr>\n<tr>\n<td><code v-pre>Int &amp; &gt;= 18</code></td>\n<td>integers from 18 up</td>\n</tr>\n<tr>\n<td><code v-pre>:standard | :premium</code></td>\n<td>one of two atoms</td>\n</tr>\n<tr>\n<td><code v-pre>{name: String, age: Int &amp; &gt;= 0}</code></td>\n<td>a closed record</td>\n</tr>\n<tr>\n<td><code v-pre>{name: String, age: Int &amp; &gt;= 0, ...}</code></td>\n<td>an open record allowing extra fields</td>\n</tr>\n</tbody>\n</table>\n<p><code v-pre>Int &amp; &gt;= 18</code> is a <strong>refinement</strong>: <code v-pre>Int</code> with a condition attached. The values are still plain integers. The condition only excludes some of them. Shapes with named fields, like the last two, are <strong>structural shapes</strong>. The others are atomic shapes. Both kinds work the same way everywhere below.</p>\n<p>Shapes are ordinary values. You can bind one to a variable, return it from a function, and combine it with other shapes at runtime:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> adult_check</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">age</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    let</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> adult</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> >=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 18</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    age</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> adult</span></span></code></pre>\n</div><div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>adult_check(20) => 20</span></span>\n<span class=\"line\"><span>adult_check(15) => Never([#{:code => :conflict,</span></span>\n<span class=\"line\"><span>                            :message => \"Expected value >= 18, got 15\", ...}])</span></span></code></pre>\n</div><p>The <code v-pre>&amp;</code> in that function is the most important operator in the language, and it has a name.</p>\n<h2 id=\"unification\" tabindex=\"-1\">Unification <a class=\"header-anchor\" href=\"#unification\" aria-label=\"Permalink to &quot;Unification&quot;\">&ZeroWidthSpace;</a></h2>\n<p><a href=\"/glossary/unification.html\">Unification</a>, written <code v-pre>&amp;</code>, combines two descriptions of a value into one that satisfies both, or reports that no such value exists. You already use it every time you write a refinement: <code v-pre>Int &amp; &gt;= 18</code> is the shape <code v-pre>Int</code> unified with the predicate <code v-pre>&gt;= 18</code>.</p>\n<p>What it does depends on what you give it, but the idea is always the same: <em>keep everything both sides say, and fail if they disagree</em>.</p>\n<p><strong>A value and a shape.</strong> The result is the value, if the shape allows it. This is validation:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> person_check</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">data</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Map</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    data</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">age</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">...</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>person_check(#{:name => \"Ann\", :age => 0})</span></span>\n<span class=\"line\"><span>  => Never([#{:code => :conflict, :message => \"Expected value > 0, got 0\",</span></span>\n<span class=\"line\"><span>              :path => [:age], ...}])</span></span></code></pre>\n</div><p>The error says which field broke which rule. The <code v-pre>...</code> makes the shape <em>open</em>: data can carry fields the shape does not mention.</p>\n<p><strong>Two shapes.</strong> The result is a shape that requires both. You can build requirements from pieces:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> merged</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">data</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Any</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    let</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> named</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">...</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    let</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> aged</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">age</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">...</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    data</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> (</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">named</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> aged</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span></code></pre>\n</div><div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>merged(#{:name => \"Ann\", :age => 3}) => UnifiedStruct(anonymous){name: \"Ann\", age: 3}</span></span>\n<span class=\"line\"><span>merged(#{:name => 7, :age => 3})</span></span>\n<span class=\"line\"><span>  => Never([#{:code => :type_mismatch, :message => \"Expected 'String', got 7\",</span></span>\n<span class=\"line\"><span>              :path => [:name], ...}])</span></span></code></pre>\n</div><p><strong>Two values.</strong> The result merges them if they agree, and fails on the first key where they don't:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>#{:a => 1} &#x26; #{:b => 2} => #{:a => 1, :b => 2}</span></span>\n<span class=\"line\"><span>#{:a => 1} &#x26; #{:a => 2} => Never([#{:code => :value_conflict, :path => [:a], ...}])</span></span></code></pre>\n</div><p>When the compiler can prove that no value satisfies both sides, it reports the conflict before anything runs. Writing <code v-pre>1 &amp; 2</code> is a type error, &quot;<code v-pre>1 &amp; 2</code> denotes no value&quot;. Two map literals that disagree, as above, are caught at runtime.</p>\n<p><strong>Data that is not complete yet.</strong> This is where unification earns its keep: it accepts data that is not complete yet. Give it a map that has a name but no age:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> known_so_far</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">data</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Any</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    data</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">age</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">...</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><p>The result is not an error. It is a value with <code v-pre>name</code> set and <code v-pre>age</code> still open: it records that an age must be a positive integer, but not which one. Unify it later with the rest of the data and it completes. Ask for a finished value too early, with <code v-pre>require_concrete</code>, and you get a precise answer instead of a crash:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>Error(#{:code => :incomplete,</span></span>\n<span class=\"line\"><span>        :message => \"Value is not concrete: field `age` is not fully specified\", ...})</span></span></code></pre>\n</div><p>That is how Bondy handles a form completed over several steps, or a config assembled from several files. You unify each piece in as it arrives, and a wrong piece fails at once with its path. The value is usable only once it is complete. There is one set of rules, not one validator per stage.</p>\n<p>A failed unification produces a <code v-pre>Never</code> (the bottom type) value carrying the reasons, not an exception, so you decide what to do with it. The same operation appears across the language: matching a pattern in a function head is one-sided unification. So is matching a fact in a rule, which the next posts rely on.</p>\n<h2 id=\"structural-types\" tabindex=\"-1\">Structural types <a class=\"header-anchor\" href=\"#structural-types\" aria-label=\"Permalink to &quot;Structural types&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <strong>structural type</strong> is a structural shape with a name (a.k.a. brand):</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Config</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:  </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =&#x3C;</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10000</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    refill_rate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><p>The name matters. It gives <code v-pre>Config</code> an identity: a plain map with the right keys is not a <code v-pre>Config</code> until you build one. A function that expects a <code v-pre>Config</code> does not accept a map. So the checks run in one place, when a value becomes a <code v-pre>Config</code>, and never again after that.</p>\n<p>There are three ways to build one, and they differ in how much they check.</p>\n<p><strong>1. <code v-pre>Config(...)</code> checks everything.</strong> With a literal, the compiler does the check:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> bad</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Config</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">3</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">refill_rate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span></code></pre>\n</div><div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>-- TYPE CHECK FAILED ---------------------------------</span></span>\n<span class=\"line\"><span>  |</span></span>\n<span class=\"line\"><span>9 |     Config(max_tokens: -3, refill_rate: 1)</span></span>\n<span class=\"line\"><span>  |     ^ field max_tokens: a value of type -3 is not a value of the declared field type Int &#x26; > 0 &#x26; =&#x3C; 10000</span></span></code></pre>\n</div><p>With a value that only exists at runtime, the same predicate runs when the constructor is called:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> from_input</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">refill_rate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Config</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">refill_rate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">refill_rate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span></code></pre>\n</div><div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>from_input(-3, 5) => error: runtime — cannot construct Config: Expected value > 0, got -3 (field `max_tokens`)</span></span></code></pre>\n</div><p>Look at the two snippets again. The refinement you wrote for the compiler is the one the runtime enforces.</p>\n<p><strong>2. <code v-pre>construct(data, Config)</code> checks without crashing.</strong> It takes a map from the outside world and returns <code v-pre>Ok</code> or <code v-pre>Error</code>, with the path of every field that failed:</p>\n<div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>construct(#{:max_tokens => 20000, :refill_rate => 5}, Config)</span></span>\n<span class=\"line\"><span>  => Error(#{:code => :construct_failed,</span></span>\n<span class=\"line\"><span>             :message => \"Unification failed\",</span></span>\n<span class=\"line\"><span>             :reasons => [#{:message => \"Expected value =&#x3C; 10000, got 20000\",</span></span>\n<span class=\"line\"><span>                            :path => [:max_tokens], ...}]})</span></span></code></pre>\n</div><p>This is the one to use at the edge of your system.</p>\n<p><strong>3. <code v-pre>Config!(...)</code> trusts you.</strong> It builds the value and skips the runtime check: given a <code v-pre>max_tokens</code> of <code v-pre>-3</code> that only exists at runtime, <code v-pre>Config!(max_tokens: max_tokens, refill_rate: 5)</code> returns <code v-pre>Config(max_tokens: -3, refill_rate: 5)</code>. The compiler still refuses what it can see is wrong, such as a literal <code v-pre>-3</code> or a <code v-pre>String</code> where an <code v-pre>Int</code> belongs. It does not demand proof of <code v-pre>&gt; 0</code>. Use it for values you already know are good, which is most values once they are inside the system.</p>\n<p>Fields can also say whether the data must supply them, and a string field can carry a pattern (<code v-pre>=~</code>):</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> User</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    name</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:   </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    age</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:    </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> >=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    email</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> String</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =~</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\"> \"^</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">[</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">^@</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#FF7B72\">\\\\</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">s</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">]</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">+@</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">[</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">^@</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#FF7B72\">\\\\</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">s</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">]</span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">+$\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,  </span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% may be absent; checked when present</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    tls</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">!:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">   Bool</span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">                                %% must be supplied by the caller</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>construct(#{:name => \"Ann\", :age => 30}, User)</span></span>\n<span class=\"line\"><span>  => Error(... \"Required field tls must be provided ...\" ...)</span></span>\n<span class=\"line\"><span>construct(#{:name => \"Ann\", :age => 30, :tls => true}, User)</span></span>\n<span class=\"line\"><span>  => Ok(User(name: \"Ann\", age: 30, tls: true))</span></span>\n<span class=\"line\"><span>construct(#{:name => \"Ann\", :age => 30, :tls => true, :email => \"ann\"}, User)</span></span>\n<span class=\"line\"><span>  => Error(... \"Value \\\"ann\\\" does not match pattern ...\" ... :path => [:email] ...)</span></span></code></pre>\n</div><p>And a field can be computed from its siblings. The leading dot means &quot;this value's field&quot;:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Invoice</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    quantity</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:   </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    unit_price</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Float</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    subtotal</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:   .unit_price </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .quantity,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    tax</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:        .subtotal </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0.1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    total</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:      .subtotal </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">+</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .tax</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><div class=\"language- vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\"></span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span>Invoice(quantity: 3, unit_price: 10.0)</span></span>\n<span class=\"line\"><span>  => Invoice(quantity: 3, unit_price: 10.0, subtotal: 30.0, tax: 3.0, total: 33.0)</span></span></code></pre>\n</div><p>The fields are computed in dependency order, from the fields you supply, so the arithmetic that used to sit in a constructor lives in the type.</p>\n<h2 id=\"rules-between-fields\" tabindex=\"-1\">Rules between fields <a class=\"header-anchor\" href=\"#rules-between-fields\" aria-label=\"Permalink to &quot;Rules between fields&quot;\">&ZeroWidthSpace;</a></h2>\n<p>That leaves the rules from the second post, the ones no schema DSL could hold: the order line's <code v-pre>new/1</code>. A discount must not exceed a tenth of the total, and a total above 10000 needs an approver. In Bondy they go on the fields, with <code v-pre>when</code>:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Amounts are in pence.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> OrderLine</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    price</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:        </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    quantity</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:     </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    total</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:        .price </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .quantity </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .total </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=&#x3C;</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10000</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> or</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .approved_by </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">!==</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> null</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    discount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:     </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> >=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .discount </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =&#x3C;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .total,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    approved_by</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> String</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><p>The 10% rule is written as <code v-pre>.discount * 10 =&lt; .total</code>: whole-number arithmetic, so the check never divides and needs no rounding policy. The approval rule sits on <code v-pre>total</code>, not on <code v-pre>approved_by</code>, because a <code v-pre>when</code> on an optional field runs only when the field is there. An absent <code v-pre>approved_by</code> reads as <code v-pre>null</code>. These are the results of <code v-pre>construct</code> on seven order lines, straight from the program:</p>\n<table tabindex=\"0\">\n<thead>\n<tr>\n<th>price × quantity</th>\n<th>discount</th>\n<th>approved_by</th>\n<th>case</th>\n<th>result</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>100 × 3</td>\n<td>—</td>\n<td>—</td>\n<td>no discount given</td>\n<td>accepted, with <code v-pre>discount: 0</code></td>\n</tr>\n<tr>\n<td>100 × 3</td>\n<td>30</td>\n<td>—</td>\n<td>exactly 10%</td>\n<td>accepted</td>\n</tr>\n<tr>\n<td>100 × 3</td>\n<td>31</td>\n<td>—</td>\n<td>10% and a penny</td>\n<td>rejected: the <code v-pre>when</code> rule fails at <code v-pre>discount</code></td>\n</tr>\n<tr>\n<td>100 × 3</td>\n<td>-1</td>\n<td>—</td>\n<td>negative</td>\n<td>rejected: &quot;Expected value &gt;= 0, got -1&quot; at <code v-pre>discount</code></td>\n</tr>\n<tr>\n<td>5000 × 2</td>\n<td>—</td>\n<td>—</td>\n<td>total of exactly 10000</td>\n<td>accepted</td>\n</tr>\n<tr>\n<td>5001 × 2</td>\n<td>—</td>\n<td>—</td>\n<td>large order, no approver</td>\n<td>rejected: the <code v-pre>when</code> rule fails at <code v-pre>total</code></td>\n</tr>\n<tr>\n<td>5001 × 2</td>\n<td>—</td>\n<td><code v-pre>&quot;ana&quot;</code></td>\n<td>large order, approved</td>\n<td>accepted</td>\n</tr>\n</tbody>\n</table>\n<p>In an Ecto changeset these rules live on one write path, and hold only for values that pass through that function. A code path that skips the changeset skips the rules. Here they live on the type, so every <code v-pre>OrderLine</code> in the system satisfies them, whichever path built it.</p>\n<h2 id=\"what-gets-checked-and-when\" tabindex=\"-1\">What gets checked, and when <a class=\"header-anchor\" href=\"#what-gets-checked-and-when\" aria-label=\"Permalink to &quot;What gets checked, and when&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A shape does not make checking free. It changes where the check lives and how often you write it.</p>\n<ul>\n<li><strong>Known at compile time:</strong> a literal that breaks a refinement never compiles. Neither does a meet the compiler can prove empty, such as <code v-pre>1 &amp; 2</code>.</li>\n<li><strong>Known only at runtime:</strong> the check runs once, where the value is built or unified, and returns the field path that failed. After that the value is trusted.</li>\n<li><strong>Vouched for:</strong> <code v-pre>Config!(...)</code> skips the runtime check.</li>\n<li><strong>Not complete yet:</strong> unification keeps what is known, fails early on anything wrong, and tells you exactly which field is still missing.</li>\n</ul>\n<p>You still pay for the checks the compiler cannot prove away. What changes is that you stop writing them by hand, and stop scattering them across every path that might forget one.</p>\n<h2 id=\"from-one-value-to-many\" tabindex=\"-1\">From one value to many <a class=\"header-anchor\" href=\"#from-one-value-to-many\" aria-label=\"Permalink to &quot;From one value to many&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Everything in this post checks one value, in one direction: the data arrives, and the shape says yes or no. A <code v-pre>when</code> on a field sees the other fields of the same value and nothing else.</p>\n<p>Real systems have rules that span many values: a request is allowed only while its bucket has tokens, an alarm clears only when someone acknowledges it. Those rules are not attached to a type. They watch a collection of values and act when a combination of them appears. That is the next noun, the rule, and the next post.</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p><a href=\"https://www.erlang.org/doc/apps/dialyzer/dialyzer.html\" target=\"_blank\" rel=\"noreferrer\">Dialyzer</a> is a <strong>DI</strong>screpancy <strong>A</strong>na<strong>LYZ</strong>er for <strong>ER</strong>lang programs. It is a static analysis tool that identifies software discrepancies in single Erlang modules or an entire codebase. Examples are definite type errors, code that is unreachable because of programming errors, and unnecessary tests. <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
            "url": "https://bondy.io/posts/2026-10-12-the-type-that-validates",
            "title": "The type that validates",
            "summary": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>Data validation is one of the most boring and error-prone (yet critical) jobs we do. It happens at the edges of our systems, where data arrives from a request, a config file or another service. It has to happen before the data means anything. Once the data is inside, code can trust it and not check it again.</p>\n<p>Here is how that usually looks for a config record in Erlang:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">    max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> pos_integer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">    refill_rate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> pos_integer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">spec</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">map</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">().</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(#{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Max, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">refill_rate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Rate} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> M)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  when</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> is_integer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Max), Max </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Max </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=&#x3C;</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10000</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">       is_integer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Rate), Rate </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    M;</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">_</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">    error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">badarg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">).</span></span></code></pre>\n</div><p>Count the places the same fact lives. The <code v-pre>-type</code> says <code v-pre>max_tokens</code> is a positive integer, for Dialyzer[^Dialyzer]. The guard says it again, for the runtime. The ceiling of <code v-pre>10000</code> appears only in the guard, because the type spec cannot express it. So the type is now a rough summary of the guard rather than the truth.</p>\n<p>And the whole thing covers one way of building a config. Add optional fields, or a config that arrives in pieces, and you write another function with another copy of the rules.</p>\n<p>The second post showed the other half of the problem: rules that relate two fields, like &quot;a discount must not exceed a tenth of the total&quot;. They don't fit in any schema DSL, so they become control flow. This post covers both halves.</p>\n<h2 id=\"shapes\" tabindex=\"-1\">Shapes <a class=\"header-anchor\" href=\"#shapes\" aria-label=\"Permalink to &quot;Shapes&quot;\">&ZeroWidthSpace;</a></h2>\n<p>In Bondy, a <a href=\"/glossary/shape.html\">shape</a> is a value that describes which values are allowed. These are all shapes:</p>\n<p>|Expression|Meaning|\n|</p>\n",
            "date_modified": "2026-10-12T00:00:00.000Z",
            "author": {
                "name": "Alejandro M. Ramallo"
            },
            "tags": [
                "Language",
                "Shapes",
                "Types",
                "Validation"
            ]
        },
        {
            "id": "https://bondy.io/posts/2026-10-12-one-noun-more-roles",
            "content_html": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<blockquote>\n<p>To <em>complect</em> is to take two or more separate concepts and entangle them so tightly that you can no longer reason about or change one without risking the other.</p>\n</blockquote>\n<p>Rich Hickey, the creator of Clojure, revived this old word to name the root cause of software complexity. His point is that complexity doesn't come from size or age. It comes from tying together things that should have stayed apart. Every &quot;we can't touch that without breaking something else&quot; is one of those knots. His prescription is to keep separate things separate: plain maps and sets instead of objects, values instead of mutable state, components side by side instead of twisted together.</p>\n<p>I think Rich is right, and I try to follow this. But there is a gap in it, and the gap is where most of the day-to-day pain in a real codebase lives.</p>\n<h2 id=\"the-gap\" tabindex=\"-1\">The gap <a class=\"header-anchor\" href=\"#the-gap\" aria-label=\"Permalink to &quot;The gap&quot;\">&ZeroWidthSpace;</a></h2>\n<p>If your data is &quot;just&quot; maps and sets, then everything you know <em>about</em> that data has nowhere to live except in your code. A bare map is typed by convention and by hope. Suppose you want to say &quot;this map must have a <code v-pre>name</code> and an <code v-pre>age</code>, and the <code v-pre>age</code> is a non-negative integer below 130&quot;. The moment you do, you have left the data model and entered procedure.</p>\n<p>So we write validators. On a good day we reach for a declarative DSL: <code v-pre>Ecto</code> schemas and <code v-pre>NimbleOptions</code> in Elixir, <code v-pre>babel</code> and <code v-pre>maps_utils</code> in Erlang (at Leasight), JSON Schema, <code v-pre>clojure.spec</code>, <code v-pre>malli</code>. These are good tools. But each is a second language bolted onto the first, and each can say some of what you mean and not the rest. Here is where the declarative part stops:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">example</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">export</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">spec</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]).</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">export</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% What the DSL can say: shape and per-field type.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">spec</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        price</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            required</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            datatype</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> pos_integer</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        quantity</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            required</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            datatype</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> pos_integer</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        discount</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            required</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> false</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            datatype</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> non_neg_integer</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        approved_by</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            required</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> false</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            datatype</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> binary</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    }.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% What the DSL cannot say. Three constraints as declarative as the</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% four above, written as control flow because the spec has no slot</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% for them.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Map0) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Map </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> maps_utils</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">validate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Map0, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">spec</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">price</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Price, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">quantity</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Qty} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Map,</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Total </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Price </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Qty,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">    %% Default to zero</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Discount </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> maps</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">get</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">discount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Map, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">    %% Cross-field: discount may not exceed 10% of the line.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Discount </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=&#x3C;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Total </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">div</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> orelse</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">invalid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">discount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}),</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">    %% Conditional on a computed value: large orders need </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">    %% an approver.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    case</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Total </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10000</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> andalso</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> not</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> is_map_key</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">approved_by</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Map) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">of</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">            error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">missing</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">approved_by</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">});</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        false</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            ok</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Map.</span></span></code></pre>\n</div><p>Read <code v-pre>spec/0</code> and you learn the shape and the types. Read <code v-pre>new/1</code> and you learn nothing until you execute it in your head. Then you learn that <code v-pre>Total</code> is derived, that <code v-pre>discount</code> is bounded by it, and that <code v-pre>approved_by</code> is required <em>sometimes</em>. The condition lives three lines away from the field. &quot;Discount is at most a tenth of the total&quot; is as much a rule about the data as <code v-pre>pos_integer</code> is. But the DSL only understands one field at a time, so anything that involves two fields becomes code.</p>\n<p>We complected the constraints into the constructor. Not by choice. In a vocabulary of maps, sets and functions a value can only ever be one thing, a datum. &quot;Discount is at most a tenth of the total&quot; is not a datum and it is not a function. It is a statement about what must hold, and the language has nowhere to put one.</p>\n<h2 id=\"one-noun-more-roles\" tabindex=\"-1\">One noun, more roles <a class=\"header-anchor\" href=\"#one-noun-more-roles\" aria-label=\"Permalink to &quot;One noun, more roles&quot;\">&ZeroWidthSpace;</a></h2>\n<p>My conclusion is that the language should let the noun it already has play more roles.</p>\n<p>In most languages a value is one thing: a datum in a variable, waiting to be passed to a function. But in every real program the same value lives other lives.</p>\n<p>That value is a fact that <em>holds</em>, a <strong>relation</strong>. It is an invariant that is <em>imposed</em>, a <strong>constraint</strong>. It is something that <em>happened</em>, an <strong>event</strong>. It is a truth that <em>persists</em> until something ends it, a <strong>fluent</strong>. These are not four data structures. They are four roles one typed value can play, each with its own semantics.</p>\n<p>The computation side is just as thin. There is one noun, the function, and one verb, <em>call</em>. Two definitions are missing beside it: the <strong>rule</strong>, which runs when the world makes it true, and the <strong>signal</strong>, which never runs at all because it always equals its definition.</p>\n<p>For almost every one of these concepts, there is an entire programming paradigm built by taking that single idea seriously and making it a primitive.<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\">[1]</a></sup> Each paradigm discovered one element, turned it into its entire world, and made the others awkward to use. I believe the working programmer needs them all—in the same program, at the same time. At least, I always have. When a language only supplies one or two, we are forced to awkwardly bolt the rest onto whatever tools are at hand.</p>\n<p>This is why I don't describe Bondy as multi-paradigm. It has one way to describe data, the <a href=\"/glossary/shape.html\">shape</a>, and one type system. You describe a kind of value once, as a named shape. Its roles are one-line declarations on top of it, and this compiles today:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% The order line from new/1. Amounts are in pence.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> OrderLine</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    price</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:        </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    quantity</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:     </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    total</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:        .price </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .quantity </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .total </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=&#x3C;</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10000</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> or</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .approved_by </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">!==</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> null</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    discount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:     </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> >=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .discount </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =&#x3C;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .total,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    approved_by</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> String</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Its two roles</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> OrderLine</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> OrderLine</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% A type and its role, declared together</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> LineApproved</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">line</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">OrderLine</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">by</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><p>Every rule from <code v-pre>new/1</code> is in the type. <code v-pre>total</code> is computed from its siblings. <code v-pre>discount</code> defaults to zero and may not exceed a tenth of the total. A total above 10000 needs an <code v-pre>approved_by</code>. The <code v-pre>when</code> on a field can read the other fields of the same value, so a rule that relates two fields sits next to the fields it relates.</p>\n<p>That is my answer to the obvious objection, that four roles and two nouns sounds like six little languages bolted onto a seventh. They are not bolted on, because they share the same values. An <code v-pre>OrderLine</code> in a query, an <code v-pre>OrderLine</code> in a rule and an <code v-pre>OrderLine</code> arriving at the boundary are the same typed value, checked by the same type checker.</p>\n<p>It also fits Hickey's own line between <em>simple</em> (not tangled up) and <em>easy</em> (familiar). A language where a value can only play one role makes you tangle the others into your code. That is complex in his sense, even with fewer keywords. A few separate roles, each doing one thing, is simpler, even if there is more to learn.</p>\n<p>Alan Perlis’ famous <a href=\"https://www.cs.yale.edu/homes/perlis-alan/quotes.html\" target=\"_blank\" rel=\"noreferrer\">epigram</a> states that <em>&quot;It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures&quot;</em>. There is still one data structure here. What multiplies is what it is allowed to mean.</p>\n<p>Three words I'll lean on. A <em>noun</em> is a kind of thing you can declare and name. A <em>verb</em> is an operation only that thing supports. A <em>role</em> is what a value is doing at runtime.</p>\n<h2 id=\"shapes\" tabindex=\"-1\">Shapes <a class=\"header-anchor\" href=\"#shapes\" aria-label=\"Permalink to &quot;Shapes&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A shape answers one question: <strong>which values are allowed?</strong> <code v-pre>Int</code> is a shape. So is <code v-pre>Int &amp; &gt;= 18</code>, the integers from 18 up, and so is <code v-pre>{name: String, age: Int &amp; &gt;= 0}</code>, a record with two fields. A shape with named fields is a <em>structural</em> shape, and when you give one a name, as in <code v-pre>type Person = {...}</code>, you get a <strong>structural type</strong>.</p>\n<p>Shapes are values in their own right. You can bind one to a variable, pass it around, and combine it with others at runtime:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">let</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> adult</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> >=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 18</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">age</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> adult</span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">     %% the age if it is 18 or more; a failure saying why, if not</span></span></code></pre>\n</div><p>The <code v-pre>&amp;</code> is <a href=\"/glossary/unification.html\">unification</a>: it combines two descriptions into one that satisfies both, or reports that they conflict. <code v-pre>Int &amp; &gt;= 18</code> is itself a unification, of <code v-pre>Int</code> with <code v-pre>&gt;= 18</code>. Unify a value with a shape and you have validation. Unify two shapes and you have a stricter shape. Unify data that is only half there and you get a value that knows which fields are still missing. The <a href=\"./2026-10-12-the-type-that-validates.html\">third post</a> shows each of these running.</p>\n<p>What makes &quot;object&quot; a slur in Hickey's vocabulary is not the record but everything OO added to it: identity, mutable state behind that identity, behaviour dispatched through it. Keep the record, throw the rest overboard, and you have what a bare map cannot give you: a description of which values are allowed, with the rules inside it. <code v-pre>discount: Int &amp; &gt;= 0 = 0 when .discount * 10 =&lt; .total</code> is part of the type, not a check performed downstream. That settles the gap inside one value. It does not settle what must hold <em>between</em> values, or what follows when it does. That takes roles.</p>\n<h2 id=\"relations-a-value-held\" tabindex=\"-1\">Relations: a value, held <a class=\"header-anchor\" href=\"#relations-a-value-held\" aria-label=\"Permalink to &quot;Relations: a value, held&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A relation answers: <strong>how are things connected?</strong> It is Codd's relational model, the one under every SQL database and <a href=\"/glossary/datalog.html\">Datalog</a>. A value on its own describes one thing. A value in the <a href=\"/glossary/relation.html\">relation</a> role is a fact, and a set of facts describes how things are connected, which you can query and join.</p>\n<p>What it replaces in your code is the object graph. <code v-pre>order.customer</code> and <code v-pre>customer.orders</code> are the same connection stored twice, kept in sync by hope, and each only works in one direction. A relation stores the connection once, and you query it from either side.</p>\n<p>Its verbs are Codd's: <strong>select</strong>, <strong>project</strong>, <strong>join</strong>, <strong>insert</strong>, <strong>delete</strong>. There is no <em>update</em>. Update is delete-then-insert, and pretending otherwise is how the object graph smuggled mutation back in.</p>\n<h2 id=\"constraints-a-value-imposed\" tabindex=\"-1\">Constraints: a value, imposed <a class=\"header-anchor\" href=\"#constraints-a-value-imposed\" aria-label=\"Permalink to &quot;Constraints: a value, imposed&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <a href=\"/glossary/constraint.html\">constraint</a> answers: <strong>what must hold?</strong> A relation is a set of facts you consult. A constraint is a fact you impose: you tell it to a store, and the store is then obliged to keep it consistent with everything else it has been told.</p>\n<p>Inside one value, the shape already holds what fell off the edge of the validation DSL and landed in <code v-pre>new/1</code>. A constraint takes the same idea across values. The invariant still lives with the type, not in a procedure the next reader has to execute in their head. And a constraint is not only something to check after the fact. Other facts can be derived from it: if the store knows <code v-pre>age &gt;= 0</code> and learns <code v-pre>age = x - 1</code>, it follows that <code v-pre>x &gt;= 1</code>.</p>\n<p>In Bondy, rules over the store are how you say what follows. Validation is the simplest case, where you already have all the values and only ask yes or no.</p>\n<p>Its verbs are <strong>tell</strong> and <strong>ask</strong>: tell adds a constraint to the store, ask checks whether what the store knows already implies one. A schema has one verb, <em>validate</em>, and validate is tell-everything-then-ask-once. A constraint you can only validate has most of its meaning cut off.</p>\n<p>The natural objection, &quot;isn't a constraint just a rule?&quot;, draws the line. A constraint is what must be true. A rule is <em>when this, then that</em>. One is a role a value plays. The other is a noun, and it gets its own section.</p>\n<h2 id=\"events-a-value-emitted\" tabindex=\"-1\">Events: a value, emitted <a class=\"header-anchor\" href=\"#events-a-value-emitted\" aria-label=\"Permalink to &quot;Events: a value, emitted&quot;\">&ZeroWidthSpace;</a></h2>\n<p>An <a href=\"/glossary/event.html\">event</a> answers: <strong>what happened?</strong> It has an instant, not a duration. It is never updated and never retracted, because it already happened.</p>\n<p>What it replaces is the callback and the log line. In most languages &quot;something happened&quot; is either a function call or a string in a log. The call welds the occurrence to whoever is listening, and the log line keeps the occurrence but throws away its type. An event keeps both: a typed value, addressed to nobody in particular. In Bondy, events are what rules <strong>emit</strong> and what processes act <strong>on</strong>. What comes in is told to the store as a constraint, and what goes out is an event.</p>\n<p>An event has no inertia at all. The next role is nothing but inertia.</p>\n<h2 id=\"fluents-a-value-latched\" tabindex=\"-1\">Fluents: a value, latched <a class=\"header-anchor\" href=\"#fluents-a-value-latched\" aria-label=\"Permalink to &quot;Fluents: a value, latched&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <a href=\"/glossary/fluent.html\">fluent</a> answers: <strong>what is true now, and since when?</strong> The idea comes from the event calculus: a fluent becomes true when something starts it and stays true until something ends it. &quot;The light is on&quot; becomes true when someone flips the switch and stays true until someone flips it back.</p>\n<p>The example that shows why this is its own role is an alarm. Define &quot;alarm is active&quot; as <code v-pre>temperature &gt; 100</code> and the alarm clears itself the instant the reading dips under the threshold. Real alarms latch. The alarm is <strong>initiated</strong> by the crossing, <strong>terminated</strong> by an acknowledgement, and in between it holds regardless of what the temperature does. That is what a mutable boolean throws away. The boolean has no record of when it changed or what changed it, and you spend the rest of the project reconstructing that from logs.</p>\n<p>The query is <strong>holds-at</strong>: was this true at time <em>t</em>? Nothing <em>sets</em> a fluent. The only way to change one is to name what changed it.</p>\n<p>Fluents are designed and on the roadmap. The <a href=\"./2026-10-12-the-alarm-that-cleared-itself.html\">post on time</a> covers the design.</p>\n<h2 id=\"signals\" tabindex=\"-1\">Signals <a class=\"header-anchor\" href=\"#signals\" aria-label=\"Permalink to &quot;Signals&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <a href=\"/glossary/signal.html\">signal</a> answers: <strong>what value does this always equal?</strong> The cleanest intuition is a spreadsheet cell. <code v-pre>B1 = A1 * 2</code> is a signal. You never assign to <code v-pre>B1</code>: it always equals twice <code v-pre>A1</code>, and keeping that true is not your problem. Its verbs are <strong>define</strong> and <strong>sample</strong>. There is no <em>set</em>, because <code v-pre>B1 = A1 * 2</code> would be meaningless if anyone could write to <code v-pre>B1</code>.</p>\n<p>A signal is not a role, because it is not a fact playing a part. A signal is a definition, like a function. A function is a computation you call. A signal is a computation you never call, because its answer is already there.</p>\n<p>The alarm shows why fluents and signals are two things. The temperature is a signal: it always has a current value, computed from the sensor, and it keeps no history. &quot;Temperature exceeds 100&quot; is a signal too: it is true while the reading is above 100 and false as soon as it is not. The moment the reading goes above 100 is an event: it happens once, at one point in time. The latched alarm is a fluent: that event makes it true, and it stays true until someone acknowledges it, even after the temperature falls. Defining the alarm as <code v-pre>temperature &gt; 100</code> treats all four as one signal, and that is why it clears itself.</p>\n<p>Signals are designed alongside fluents, on the same incremental engine.</p>\n<h2 id=\"rules\" tabindex=\"-1\">Rules <a class=\"header-anchor\" href=\"#rules\" aria-label=\"Permalink to &quot;Rules&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <a href=\"/glossary/rule.html\">rule</a> answers: <strong>what follows, and what reacts?</strong> It has two kinds, one for relations and one for constraints.</p>\n<p><em>Derivation rules</em> work over relations: they work out new facts from existing ones, until nothing new appears. This is Datalog, and it ships:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Edge</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">to</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reachable</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">src</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">dst</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Edge</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">src</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">to</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">dst</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reachable</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">src</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">dst</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Edge</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">src</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">to</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">mid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Reachable</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">src</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">mid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">dst</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">dst</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span></code></pre>\n</div><p><em>Rewrite rules</em> work over constraints: they match several facts in a store at once, check a condition, and replace what they matched. This is CHR, <a href=\"https://dtai.cs.kuleuven.be/projects/CHR/about.shtml\" target=\"_blank\" rel=\"noreferrer\">Constraint Handling Rules</a>, and it ships too. A rule matches facts with <strong>on</strong>, takes the ones it names out of the store with <strong>remove</strong>, and adds constraints and events with <strong>tell</strong>. The facts it does not remove stay in the store.</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Request</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">      =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> consume_token</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">         Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p>Matching several facts at once is what a function can't do. You call a function with its input. A rule matches a <em>combination</em> of facts in the store (<code v-pre>rid</code> in both patterns is a join), and the engine decides when it fires.</p>\n<blockquote>\n<p>A function runs when called. A rule runs when the world makes it true.</p>\n</blockquote>\n<p>Because a rule is a declaration and not control flow, the compiler can inspect it. Today it checks that every guard has no side effects and always gives the same answer. It also checks that a guard cannot go from true to false as more facts arrive, so the order of arrival cannot change the result. Checking that a rule set always terminates is on the roadmap.</p>\n<p>Run the alarm once more, end to end. The temperature is a signal. When it crosses the threshold, the crossing is told to the store. A rewrite rule matches it, latches the alarm fluent, and emits an event so the rest of the system hears about it. When an acknowledgement arrives, another rule terminates the fluent. Four roles and two nouns, none standing in for another.</p>\n<h2 id=\"where-this-lands\" tabindex=\"-1\">Where this lands <a class=\"header-anchor\" href=\"#where-this-lands\" aria-label=\"Permalink to &quot;Where this lands&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Put them side by side and the table is clean enough that I take it as evidence these are real, not invented:</p>\n<table tabindex=\"0\">\n<thead>\n<tr>\n<th>Noun or role</th>\n<th>The question it answers</th>\n<th>Its verbs</th>\n<th>What it separates</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Shape</strong> (the noun)</td>\n<td>Which values are allowed?</td>\n<td>unify (<code v-pre>&amp;</code>)</td>\n<td>The description of a value from identity, state and behaviour</td>\n</tr>\n<tr>\n<td>as relation</td>\n<td>How are things connected?</td>\n<td>select, project, join, insert, delete</td>\n<td>A connection from the one direction you happened to store it in</td>\n</tr>\n<tr>\n<td>as constraint</td>\n<td>What must hold?</td>\n<td>tell, ask</td>\n<td>Validity from the procedure that checks it</td>\n</tr>\n<tr>\n<td>as event</td>\n<td>What happened?</td>\n<td>emit, on</td>\n<td>An occurrence from whoever is listening</td>\n</tr>\n<tr>\n<td>as fluent</td>\n<td>What is true now, and since when?</td>\n<td>initiate, terminate, holds-at</td>\n<td>A persistent truth from its cell and its cause</td>\n</tr>\n<tr>\n<td><strong>Signal</strong></td>\n<td>What value does this always equal?</td>\n<td>define, sample</td>\n<td>A derived value from the glue that recomputes it</td>\n</tr>\n<tr>\n<td><strong>Rule</strong></td>\n<td>What follows, and what reacts?</td>\n<td>match, remove, derive, fire</td>\n<td>Derivation and reaction from hand-wired control flow</td>\n</tr>\n</tbody>\n</table>\n<p>Unification belongs to the shape, and because every role is played by values of a shape, it works in all of them. It is what holds the table together: it is how a rule matches a constraint in the store and how a query matches a fact in a relation.</p>\n<p>Everything else is specific. Nothing is joined except a relation. Nothing is told except a constraint. Nothing fires except a rule. And none of the verbs is <em>assign</em>.</p>\n<p>Rich is right that complecting is the disease and that maps and sets beat the object soup they replaced. My one amendment is that maps and sets answer one of these seven questions, what is this value. Offering them as the answer to all seven is itself a complecting. You get one role and one verb, and every other life a value leads gets smeared across procedures, DSLs and mutable cells. A language that ignores relation, constraint, event, fluent, signal and rule doesn't spare you those concepts. It makes you pay for them by hand, forever, with <em>assign</em>.</p>\n<hr>\n<p>I've spent the last few years putting these under one type system, on the BEAM. Shapes, relations, constraints, events and both kinds of rule are in the compiler today. Fluents and signals are designed and come next. The rest of this series takes them one at a time, with code you can compile. It starts with shapes, and with how the compiler and the runtime check the order line above.</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>The relational model and Datalog took the relation. Constraint programming took the constraint, and concurrent constraint programming (Saraswat) gave it <em>tell</em> and <em>ask</em>. The situation and event calculi (McCarthy and Hayes, then Kowalski and Sergot) took the event and the fluent. Logic programming took the rule. Synchronous and reactive languages (Lustre, Esterel, FRP) took the signal. Object orientation took the shape of the value and welded identity, state and behaviour onto it. <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
            "url": "https://bondy.io/posts/2026-10-12-one-noun-more-roles",
            "title": "One noun, more roles",
            "summary": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new <a href=\"/glossary/declarative.html\">declarative</a> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<blockquote>\n<p>To <em>complect</em> is to take two or more separate concepts and entangle them so tightly that you can no longer reason about or change one without risking the other.</p>\n</blockquote>\n<p>Rich Hickey, the creator of Clojure, revived this old word to name the root cause of software complexity. His point is that complexity doesn't come from size or age. It comes from tying together things that should have stayed apart. Every &quot;we can't touch that without breaking something else&quot; is one of those knots. His prescription is to keep separate things separate: plain maps and sets instead of objects, values instead of mutable state, components side by side instead of twisted together.</p>\n<p>I think Rich is right, and I try to follow this. But there is a gap in it, and the gap is where most of the day-to-day pain in a real codebase lives.</p>\n<h2 id=\"the-gap\" tabindex=\"-1\">The gap <a class=\"header-anchor\" href=\"#the-gap\" aria-label=\"Permalink to &quot;The gap&quot;\">&ZeroWidthSpace;</a></h2>\n<p>If your data is &quot;just&quot; maps and sets, then everything you know <em>about</em> that data has nowhere to live except in your code. A bare map is typed by convention and by hope. Suppose you want to say &quot;this map must have a <code v-pre>name</code> and an <code v-pre>age</code>, and the <code v-pre>age</code> is a non-negative integer below 130&quot;. The moment you do, you have left the data model and entered procedure.</p>\n<p>So we write validators. On a good day we reach for a declarative DSL: <code v-pre>Ecto</code> schemas and <code v-pre>NimbleOptions</code> in Elixir, <code v-pre>babel</code> and <code v-pre>maps_utils</code> in Erlang (at Leasight), JSON Schema, <code v-pre>clojure.spec</code>, <code v-pre>malli</code>. These are good tools. But each is a second language bolted onto the first, and each can say some of what you mean and not the rest. Here is where the declarative part stops:</p>\n<div class=\"language-erlang vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">erlang</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">example</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">export</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">spec</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]).</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">-</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">export</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">/</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">]).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% What the DSL can say: shape and per-field type.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">spec</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        price</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            required</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            datatype</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> pos_integer</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        quantity</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            required</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            datatype</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> pos_integer</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        discount</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            required</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> false</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            datatype</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> non_neg_integer</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        },</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        approved_by</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> #{</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            required</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> false</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            datatype</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> binary</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    }.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% What the DSL cannot say. Three constraints as declarative as the</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% four above, written as control flow because the spec has no slot</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% for them.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">new</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Map0) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Map </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> maps_utils</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">validate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(Map0, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">spec</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    #{</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">price</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Price, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">quantity</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> :</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Qty} </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Map,</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Total </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Price </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Qty,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">    %% Default to zero</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Discount </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> maps</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">get</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">discount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Map, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">    %% Cross-field: discount may not exceed 10% of the line.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Discount </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=&#x3C;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Total </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">div</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> orelse</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">invalid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">discount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}),</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">    %% Conditional on a computed value: large orders need </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">    %% an approver.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    case</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> Total </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10000</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> andalso</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> not</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> is_map_key</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">approved_by</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, Map) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">of</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\">            error</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">({</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">missing</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">approved_by</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">});</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">        false</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> -> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">            ok</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">    Map.</span></span></code></pre>\n</div><p>Read <code v-pre>spec/0</code> and you learn the shape and the types. Read <code v-pre>new/1</code> and you learn nothing until you execute it in your head. Then you learn that <code v-pre>Total</code> is derived, that <code v-pre>discount</code> is bounded by it, and that <code v-pre>approved_by</code> is required <em>sometimes</em>. The condition lives three lines away from the field. &quot;Discount is at most a tenth of the total&quot; is as much a rule about the data as <code v-pre>pos_integer</code> is. But the DSL only understands one field at a time, so anything that involves two fields becomes code.</p>\n<p>We complected the constraints into the constructor. Not by choice. In a vocabulary of maps, sets and functions a value can only ever be one thing, a datum. &quot;Discount is at most a tenth of the total&quot; is not a datum and it is not a function. It is a statement about what must hold, and the language has nowhere to put one.</p>\n<h2 id=\"one-noun-more-roles\" tabindex=\"-1\">One noun, more roles <a class=\"header-anchor\" href=\"#one-noun-more-roles\" aria-label=\"Permalink to &quot;One noun, more roles&quot;\">&ZeroWidthSpace;</a></h2>\n<p>My conclusion is that the language should let the noun it already has play more roles.</p>\n<p>In most languages a value is one thing: a datum in a variable, waiting to be passed to a function. But in every real program the same value lives other lives.</p>\n<p>That value is a fact that <em>holds</em>, a <strong>relation</strong>. It is an invariant that is <em>imposed</em>, a <strong>constraint</strong>. It is something that <em>happened</em>, an <strong>event</strong>. It is a truth that <em>persists</em> until something ends it, a <strong>fluent</strong>. These are not four data structures. They are four roles one typed value can play, each with its own semantics.</p>\n<p>The computation side is just as thin. There is one noun, the function, and one verb, <em>call</em>. Two definitions are missing beside it: the <strong>rule</strong>, which runs when the world makes it true, and the <strong>signal</strong>, which never runs at all because it always equals its definition.</p>\n<p>For almost every one of these concepts, there is an entire programming paradigm built by taking that single idea seriously and making it a primitive.[^lineage] Each paradigm discovered one element, turned it into its entire world, and made the others awkward to use. I believe the working programmer needs them all—in the same program, at the same time. At least, I always have. When a language only supplies one or two, we are forced to awkwardly bolt the rest onto whatever tools are at hand.</p>\n<p>This is why I don't describe Bondy as multi-paradigm. It has one way to describe data, the <a href=\"/glossary/shape.html\">shape</a>, and one type system. You describe a kind of value once, as a named shape. Its roles are one-line declarations on top of it, and this compiles today:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% The order line from new/1. Amounts are in pence.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> OrderLine</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    price</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:        </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    quantity</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:     </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    total</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:        .price </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .quantity </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .total </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">=&#x3C;</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10000</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> or</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .approved_by </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">!==</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> null</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    discount</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">:     </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> >=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .discount </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">*</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =&#x3C;</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> .total,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    approved_by</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">?:</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> String</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Its two roles</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> OrderLine</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> OrderLine</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% A type and its role, declared together</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> LineApproved</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">line</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">OrderLine</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">by</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span></code></pre>\n</div><p>Every rule from <code v-pre>new/1</code> is in the type. <code v-pre>total</code> is computed from its siblings. <code v-pre>discount</code> defaults to zero and may not exceed a tenth of the total. A total above 10000 needs an <code v-pre>approved_by</code>. The <code v-pre>when</code> on a field can read the other fields of the same value, so a rule that relates two fields sits next to the fields it relates.</p>\n<p>That is my answer to the obvious objection, that four roles and two nouns sounds like six little languages bolted onto a seventh. They are not bolted on, because they share the same values. An <code v-pre>OrderLine</code> in a query, an <code v-pre>OrderLine</code> in a rule and an <code v-pre>OrderLine</code> arriving at the boundary are the same typed value, checked by the same type checker.</p>\n<p>It also fits Hickey's own line between <em>simple</em> (not tangled up) and <em>easy</em> (familiar). A language where a value can only play one role makes you tangle the others into your code. That is complex in his sense, even with fewer keywords. A few separate roles, each doing one thing, is simpler, even if there is more to learn.</p>\n<p>Alan Perlis’ famous <a href=\"https://www.cs.yale.edu/homes/perlis-alan/quotes.html\" target=\"_blank\" rel=\"noreferrer\">epigram</a> states that <em>&quot;It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures&quot;</em>. There is still one data structure here. What multiplies is what it is allowed to mean.</p>\n<p>Three words I'll lean on. A <em>noun</em> is a kind of thing you can declare and name. A <em>verb</em> is an operation only that thing supports. A <em>role</em> is what a value is doing at runtime.</p>\n<h2 id=\"shapes\" tabindex=\"-1\">Shapes <a class=\"header-anchor\" href=\"#shapes\" aria-label=\"Permalink to &quot;Shapes&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A shape answers one question: <strong>which values are allowed?</strong> <code v-pre>Int</code> is a shape. So is <code v-pre>Int &amp; &gt;= 18</code>, the integers from 18 up, and so is <code v-pre>{name: String, age: Int &amp; &gt;= 0}</code>, a record with two fields. A shape with named fields is a <em>structural</em> shape, and when you give one a name, as in <code v-pre>type Person = {...}</code>, you get a <strong>structural type</strong>.</p>\n<p>Shapes are values in their own right. You can bind one to a variable, pass it around, and combine it with others at runtime:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">let</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> adult</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> >=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 18</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">age</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> adult</span><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">     %% the age if it is 18 or more; a failure saying why, if not</span></span></code></pre>\n</div><p>The <code v-pre>&amp;</code> is <a href=\"/glossary/unification.html\">unification</a>: it combines two descriptions into one that satisfies both, or reports that they conflict. <code v-pre>Int &amp; &gt;= 18</code> is itself a unification, of <code v-pre>Int</code> with <code v-pre>&gt;= 18</code>. Unify a value with a shape and you have validation. Unify two shapes and you have a stricter shape. Unify data that is only half there and you get a value that knows which fields are still missing. The <a href=\"./2026-10-12-the-type-that-validates.html\">third post</a> shows each of these running.</p>\n<p>What makes &quot;object&quot; a slur in Hickey's vocabulary is not the record but everything OO added to it: identity, mutable state behind that identity, behaviour dispatched through it. Keep the record, throw the rest overboard, and you have what a bare map cannot give you: a description of which values are allowed, with the rules inside it. <code v-pre>discount: Int &amp; &gt;= 0 = 0 when .discount * 10 =&lt; .total</code> is part of the type, not a check performed downstream. That settles the gap inside one value. It does not settle what must hold <em>between</em> values, or what follows when it does. That takes roles.</p>\n<h2 id=\"relations-a-value-held\" tabindex=\"-1\">Relations: a value, held <a class=\"header-anchor\" href=\"#relations-a-value-held\" aria-label=\"Permalink to &quot;Relations: a value, held&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A relation answers: <strong>how are things connected?</strong> It is Codd's relational model, the one under every SQL database and <a href=\"/glossary/datalog.html\">Datalog</a>. A value on its own describes one thing. A value in the <a href=\"/glossary/relation.html\">relation</a> role is a fact, and a set of facts describes how things are connected, which you can query and join.</p>\n<p>What it replaces in your code is the object graph. <code v-pre>order.customer</code> and <code v-pre>customer.orders</code> are the same connection stored twice, kept in sync by hope, and each only works in one direction. A relation stores the connection once, and you query it from either side.</p>\n<p>Its verbs are Codd's: <strong>select</strong>, <strong>project</strong>, <strong>join</strong>, <strong>insert</strong>, <strong>delete</strong>. There is no <em>update</em>. Update is delete-then-insert, and pretending otherwise is how the object graph smuggled mutation back in.</p>\n<h2 id=\"constraints-a-value-imposed\" tabindex=\"-1\">Constraints: a value, imposed <a class=\"header-anchor\" href=\"#constraints-a-value-imposed\" aria-label=\"Permalink to &quot;Constraints: a value, imposed&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <a href=\"/glossary/constraint.html\">constraint</a> answers: <strong>what must hold?</strong> A relation is a set of facts you consult. A constraint is a fact you impose: you tell it to a store, and the store is then obliged to keep it consistent with everything else it has been told.</p>\n<p>Inside one value, the shape already holds what fell off the edge of the validation DSL and landed in <code v-pre>new/1</code>. A constraint takes the same idea across values. The invariant still lives with the type, not in a procedure the next reader has to execute in their head. And a constraint is not only something to check after the fact. Other facts can be derived from it: if the store knows <code v-pre>age &gt;= 0</code> and learns <code v-pre>age = x - 1</code>, it follows that <code v-pre>x &gt;= 1</code>.</p>\n<p>In Bondy, rules over the store are how you say what follows. Validation is the simplest case, where you already have all the values and only ask yes or no.</p>\n<p>Its verbs are <strong>tell</strong> and <strong>ask</strong>: tell adds a constraint to the store, ask checks whether what the store knows already implies one. A schema has one verb, <em>validate</em>, and validate is tell-everything-then-ask-once. A constraint you can only validate has most of its meaning cut off.</p>\n<p>The natural objection, &quot;isn't a constraint just a rule?&quot;, draws the line. A constraint is what must be true. A rule is <em>when this, then that</em>. One is a role a value plays. The other is a noun, and it gets its own section.</p>\n<h2 id=\"events-a-value-emitted\" tabindex=\"-1\">Events: a value, emitted <a class=\"header-anchor\" href=\"#events-a-value-emitted\" aria-label=\"Permalink to &quot;Events: a value, emitted&quot;\">&ZeroWidthSpace;</a></h2>\n<p>An <a href=\"/glossary/event.html\">event</a> answers: <strong>what happened?</strong> It has an instant, not a duration. It is never updated and never retracted, because it already happened.</p>\n<p>What it replaces is the callback and the log line. In most languages &quot;something happened&quot; is either a function call or a string in a log. The call welds the occurrence to whoever is listening, and the log line keeps the occurrence but throws away its type. An event keeps both: a typed value, addressed to nobody in particular. In Bondy, events are what rules <strong>emit</strong> and what processes act <strong>on</strong>. What comes in is told to the store as a constraint, and what goes out is an event.</p>\n<p>An event has no inertia at all. The next role is nothing but inertia.</p>\n<h2 id=\"fluents-a-value-latched\" tabindex=\"-1\">Fluents: a value, latched <a class=\"header-anchor\" href=\"#fluents-a-value-latched\" aria-label=\"Permalink to &quot;Fluents: a value, latched&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <a href=\"/glossary/fluent.html\">fluent</a> answers: <strong>what is true now, and since when?</strong> The idea comes from the event calculus: a fluent becomes true when something starts it and stays true until something ends it. &quot;The light is on&quot; becomes true when someone flips the switch and stays true until someone flips it back.</p>\n<p>The example that shows why this is its own role is an alarm. Define &quot;alarm is active&quot; as <code v-pre>temperature &gt; 100</code> and the alarm clears itself the instant the reading dips under the threshold. Real alarms latch. The alarm is <strong>initiated</strong> by the crossing, <strong>terminated</strong> by an acknowledgement, and in between it holds regardless of what the temperature does. That is what a mutable boolean throws away. The boolean has no record of when it changed or what changed it, and you spend the rest of the project reconstructing that from logs.</p>\n<p>The query is <strong>holds-at</strong>: was this true at time <em>t</em>? Nothing <em>sets</em> a fluent. The only way to change one is to name what changed it.</p>\n<p>Fluents are designed and on the roadmap. The <a href=\"./2026-10-12-the-alarm-that-cleared-itself.html\">post on time</a> covers the design.</p>\n<h2 id=\"signals\" tabindex=\"-1\">Signals <a class=\"header-anchor\" href=\"#signals\" aria-label=\"Permalink to &quot;Signals&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <a href=\"/glossary/signal.html\">signal</a> answers: <strong>what value does this always equal?</strong> The cleanest intuition is a spreadsheet cell. <code v-pre>B1 = A1 * 2</code> is a signal. You never assign to <code v-pre>B1</code>: it always equals twice <code v-pre>A1</code>, and keeping that true is not your problem. Its verbs are <strong>define</strong> and <strong>sample</strong>. There is no <em>set</em>, because <code v-pre>B1 = A1 * 2</code> would be meaningless if anyone could write to <code v-pre>B1</code>.</p>\n<p>A signal is not a role, because it is not a fact playing a part. A signal is a definition, like a function. A function is a computation you call. A signal is a computation you never call, because its answer is already there.</p>\n<p>The alarm shows why fluents and signals are two things. The temperature is a signal: it always has a current value, computed from the sensor, and it keeps no history. &quot;Temperature exceeds 100&quot; is a signal too: it is true while the reading is above 100 and false as soon as it is not. The moment the reading goes above 100 is an event: it happens once, at one point in time. The latched alarm is a fluent: that event makes it true, and it stays true until someone acknowledges it, even after the temperature falls. Defining the alarm as <code v-pre>temperature &gt; 100</code> treats all four as one signal, and that is why it clears itself.</p>\n<p>Signals are designed alongside fluents, on the same incremental engine.</p>\n<h2 id=\"rules\" tabindex=\"-1\">Rules <a class=\"header-anchor\" href=\"#rules\" aria-label=\"Permalink to &quot;Rules&quot;\">&ZeroWidthSpace;</a></h2>\n<p>A <a href=\"/glossary/rule.html\">rule</a> answers: <strong>what follows, and what reacts?</strong> It has two kinds, one for relations and one for constraints.</p>\n<p><em>Derivation rules</em> work over relations: they work out new facts from existing ones, until nothing new appears. This is Datalog, and it ships:</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Edge</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">to</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reachable</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">src</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">dst</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Edge</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">src</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">to</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">dst</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reachable</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">src</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">dst</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Edge</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">from</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">src</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">to</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">mid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Reachable</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">src</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">mid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">dst</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">dst</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span></code></pre>\n</div><p><em>Rewrite rules</em> work over constraints: they match several facts in a store at once, check a condition, and replace what they matched. This is CHR, <a href=\"https://dtai.cs.kuleuven.be/projects/CHR/about.shtml\" target=\"_blank\" rel=\"noreferrer\">Constraint Handling Rules</a>, and it ships too. A rule matches facts with <strong>on</strong>, takes the ones it names out of the store with <strong>remove</strong>, and adds constraints and events with <strong>tell</strong>. The facts it does not remove stay in the store.</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Request</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">      =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> consume_token</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">m</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">         Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span></code></pre>\n</div><p>Matching several facts at once is what a function can't do. You call a function with its input. A rule matches a <em>combination</em> of facts in the store (<code v-pre>rid</code> in both patterns is a join), and the engine decides when it fires.</p>\n<blockquote>\n<p>A function runs when called. A rule runs when the world makes it true.</p>\n</blockquote>\n<p>Because a rule is a declaration and not control flow, the compiler can inspect it. Today it checks that every guard has no side effects and always gives the same answer. It also checks that a guard cannot go from true to false as more facts arrive, so the order of arrival cannot change the result. Checking that a rule set always terminates is on the roadmap.</p>\n<p>Run the alarm once more, end to end. The temperature is a signal. When it crosses the threshold, the crossing is told to the store. A rewrite rule matches it, latches the alarm fluent, and emits an event so the rest of the system hears about it. When an acknowledgement arrives, another rule terminates the fluent. Four roles and two nouns, none standing in for another.</p>\n<h2 id=\"where-this-lands\" tabindex=\"-1\">Where this lands <a class=\"header-anchor\" href=\"#where-this-lands\" aria-label=\"Permalink to &quot;Where this lands&quot;\">&ZeroWidthSpace;</a></h2>\n<p>Put them side by side and the table is clean enough that I take it as evidence these are real, not invented:</p>\n<p>| Noun or role | The question it answers | Its verbs | What it separates |\n|</p>\n",
            "date_modified": "2026-10-12T00:00:00.000Z",
            "author": {
                "name": "Alejandro M. Ramallo"
            },
            "tags": [
                "Language",
                "Shapes",
                "Data Modeling"
            ]
        },
        {
            "id": "https://bondy.io/posts/2026-10-12-bondy-as-consilience",
            "content_html": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new declarative<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\">[1]</a></sup> language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>Bondy is a brand about <em>connecting everything to everything</em>. Not only in the networking sense, but in a deeper one that has shaped how I've built software for as long as I've been building it. It is the conviction that many of the things we normally keep in separate boxes were always meant to belong together. Bondy is the name I gave to that conviction.</p>\n<p>It began as the name of our distributed application networking (DAN) platform. It removed the bureaucratic middleware the market kept throwing at us: the service mesh, the API gateway, the RPC framework, the pub/sub broker. Each solved a sliver of the same problem, and each demanded its own security, its own infrastructure, its own configuration, its own learning curve. Collapsing all of that into one coherent thing was the most pressing pain in front of us at <a href=\"https://www.leapsight.com\" target=\"_blank\" rel=\"noreferrer\">Leapsight</a>, so that is where I started. But the application networking platform was only ever one piece of Bondy.</p>\n<p>The ambition behind the brand was always larger: to take a great many beautiful and inspired ideas from computer science and bring them together into a single, coherent whole. The market neglected some of them for commercial rather than technical reasons. The application networking platform solved one piece of that puzzle. It was never the puzzle.</p>\n<MindMap mono=\"green\" title=\"Bondy (application networking platform)\n    a coherent whole\" \n  caption=\"The middleware tax — a half-dozen tools and protocols — collapsed into one tool and one protocol.\">\n<ul>\n<li>Service Mesh</li>\n<li>API Gateway</li>\n<li>Service Discovery Server</li>\n<li>Authentication Server</li>\n<li>Authorization Server</li>\n<li>RPC Framework</li>\n<li>Pub/Sub Broker</li>\n</ul>\n</MindMap>\n<h2 id=\"a-drawer-full-of-ideas-that-belonged-together\" tabindex=\"-1\">A drawer full of ideas that belonged together <a class=\"header-anchor\" href=\"#a-drawer-full-of-ideas-that-belonged-together\" aria-label=\"Permalink to &quot;A drawer full of ideas that belonged together&quot;\">&ZeroWidthSpace;</a></h2>\n<blockquote>\n<p>If you do this work long enough, you stop collecting tools and start collecting convictions.</p>\n</blockquote>\n<p>Mine accumulated slowly, one project at a time. By the late nineties they lived in an <a href=\"https://eastgate.com/Tinderbox/index.html\" target=\"_blank\" rel=\"noreferrer\">Eastgate Tinderbox</a> file — my intellectual drawers made digital — and it was filling up. OpenStep, distribution, tuplespaces, Lifestreams, Xanadu, JINI, Datalog, logic and constraint programming, Meta Content Framework and RDF, FIPA agents, Erlang. Notes linked to notes linked to notes, about systems that to everyone else lived in different fields and different conferences. But to me they kept pointing at the same underlying thing.</p>\n<p>Most of those ideas were not mine. They belonged to researchers and communities far smarter than me. What was mine was a stubborn feeling that they belonged together. They were not a handful of separate problems with separate solutions, but facets of one problem the world had never let sit in the same room.</p>\n<p>For a long while they stayed in separate drawers, because the industry insisted they belonged there, or because the best technology does not always win.</p>\n<p>I remember approaching one of the engineers behind the first version of OS X Spotlight, announced at Apple's WWDC in 2004. When I asked him why Spotlight wasn't built as an RDF store, he asked me what RDF was. This was shocking, given that RDF's ancestor—the Meta Content Framework (MCF)<sup class=\"footnote-ref\"><a href=\"#fn2\" id=\"fnref2\">[2]</a></sup>—was actually created at Apple and showcased by HotSauce<sup class=\"footnote-ref\"><a href=\"#fn3\" id=\"fnref3\">[3]</a></sup>. At least now, almost 20 years later, RDF has finally found its way back to Apple<sup class=\"footnote-ref\"><a href=\"#fn4\" id=\"fnref4\">[4]</a></sup>.</p>\n<MindMap title=\"The drawers\" \n    links=\"Multiagent systems -- Actors; Linda/Tuplespaces -- JINI/Javaspaces;\n    Erlang -- Actors\" \n    caption=\"The separate drawers — and the one conviction underneath them.\">\n<ul>\n<li>Distributed Systems\n<ul>\n<li>Erlang</li>\n<li>JINI/Javaspaces</li>\n<li>CRDTs</li>\n<li>RPC</li>\n<li>Pub/Sub</li>\n</ul>\n</li>\n<li>Concurrent &amp; Parallel Programming\n<ul>\n<li>Actors</li>\n<li>Linda/Tuplespaces</li>\n</ul>\n</li>\n<li>Knowledge Representation\n<ul>\n<li>Meta Content Framework</li>\n<li>RDF/OWL\n<ul>\n<li>Knowledge Graphs</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>Data &amp; Knowledge Bases\n<ul>\n<li>Relational Model</li>\n<li>Bitemporal models</li>\n</ul>\n</li>\n<li>Dataflow Programming\n<ul>\n<li>Timely Dataflow</li>\n<li>Differential Dataflow</li>\n</ul>\n</li>\n<li>Symbolic AI, Logic &amp; Constraint programming\n<ul>\n<li>Prolog</li>\n<li>Datalog</li>\n<li>Constraint Handling Rules</li>\n<li>Neuro-symbolic Programming</li>\n</ul>\n</li>\n<li>Rule Systems\n<ul>\n<li>JESS</li>\n<li>CLIPS</li>\n</ul>\n</li>\n<li>Multiagent systems\n<ul>\n<li>FIPA</li>\n</ul>\n</li>\n<li>Interfaces\n<ul>\n<li>OpenStep</li>\n<li>OpenDocs</li>\n<li>Xanadu</li>\n<li>Lifestreams</li>\n</ul>\n</li>\n</ul>\n</MindMap>\n<h2 id=\"the-word-for-what-i-ve-been-doing\" tabindex=\"-1\">The word for what I've been doing <a class=\"header-anchor\" href=\"#the-word-for-what-i-ve-been-doing\" aria-label=\"Permalink to &quot;The word for what I've been doing&quot;\">&ZeroWidthSpace;</a></h2>\n<Quote author=\"E.O. Wilson\" source=\"Consilience: The Unity of knowledge\">\nWhen we have unified enough certain knowledge, we will understand who we are and why we are here. If those committed to the quest fail, they will be forgiven. When lost, they will find another way. The moral imperative of humanism is the endeavour alone, whether successful or not, provided the effort is honorable and failure memorable.\n</Quote>\n<BookCard cover=\"https://covers.openlibrary.org/b/isbn/9780349111124-M.jpg\" link=\"https://openlibrary.org/isbn/9780349111124\" title=\"Consilience\" subtitle=\"The Unity of Knowledge\" author=\"E. O. Wilson\" year=\"1998\" caption=\"The book that named it →\" /><p>I found the word for it in 1998, when I read <em>Consilience: The Unity of Knowledge</em> by E. O. Wilson. Wilson used it to mean the &quot;jumping together&quot; of knowledge — the way facts and theories from unrelated disciplines link up into a common groundwork of explanation. It is the opposite of specialisation: the belief that the seams between fields are artefacts of how we organised our universities, not features of reality.</p>\n<p>That named the drawer. What I'd been assembling wasn't a reading list or a wiki. It was a consilience waiting to happen.</p>\n<p>And that is what Bondy has always been reaching for. Not a product with a long feature list, but a consilience. There, the ideas I'd been carrying for decades could finally stop being neighbours and start being one thing.</p>\n<h2 id=\"why-the-whole-puzzle-needs-a-language\" tabindex=\"-1\">Why the whole puzzle needs a language <a class=\"header-anchor\" href=\"#why-the-whole-puzzle-needs-a-language\" aria-label=\"Permalink to &quot;Why the whole puzzle needs a language&quot;\">&ZeroWidthSpace;</a></h2>\n<blockquote>\n<p>The language is where the ambition was pointing the entire time.</p>\n</blockquote>\n<p>These ideas have to actually unify: not sit side by side, but compose, reinforce each other and become guarantees you can lean on. For that, they can't live in the plumbing. They have to live in the language you write your program in, and in the runtime that executes it.</p>\n<p>I want to be careful here, because it would be easy to read this as one thing turning into another: the application networking platform growing up into a language. It isn't that. Both are expressions of the same philosophy, and they solve different amounts of the puzzle. I began with the networking platform because I wasn't ready to solve the whole thing yet, and because the middleware tax was the pain right in front of us. What the language actually is — and how it makes these ideas cohere — is what the rest of this series is for.</p>\n<h2 id=\"why-now-we-need-a-declarative-language-more-than-ever\" tabindex=\"-1\">Why now: we need a declarative language more than ever <a class=\"header-anchor\" href=\"#why-now-we-need-a-declarative-language-more-than-ever\" aria-label=\"Permalink to &quot;Why now: we need a declarative language more than ever&quot;\">&ZeroWidthSpace;</a></h2>\n<p>There is a second reason, and it is newer than the drawer.</p>\n<p>Language models now write a growing share of software. That changes the economics of code, and it raises the stakes. For the systems that matter, the risk is no longer that the code cannot be produced. It is that nobody can understand, review or maintain what was produced, as it grows faster than anyone reads it.</p>\n<p>Some argue the opposite: once machines write the code, the language stops mattering. I think that gets it backwards. The less of the code a human writes, the more the language has to carry the meaning a human can check.</p>\n<p>A declarative program says <em>what</em> must be true, not the sequence of steps that happens to make it true today. It is mostly facts, rules and constraints, with little control flow in between, and that is what both people and models get right most often. A model is far more likely to get a rule, relation, constraint or type right than a long chain of imperative updates. Those updates must stay in sync across a dozen code paths. And a reviewer is far more likely to spot the one that is wrong.</p>\n<p>This is not a hypothesis. At Leapsight we see a reduction of 10x to 15x in lines of code compared to the Erlang that does the same job. The difference is the declarative part. The shapes, rules, relations and constraints that Erlang makes you spell out as control flow are one-line statements in Bondy. Less code is less to write, and above all less to review.</p>\n<p>The payoff is the same for people and for machines. Invariants live in one place, so programs are cheaper to change. Anything that can be worked out from other facts is written once as a rule, not kept in sync by hand, so there are fewer places for bugs to hide. And the program reads like its specification, so reviewing it means reading what it claims. Add a compiler that knows what every function is allowed to do, and code written by a tool can run with only the permissions you grant it. The <a href=\"./2026-10-12-what-a-function-is-allowed-to-do.html\">seventh post</a> is about that.</p>\n<p>The BEAM already gives us the operational foundation for systems that keep running. Bondy adds the semantic one. That matters most now that we write those systems together with machines.</p>\n<h2 id=\"bondy-connect-and-bondy-lang\" tabindex=\"-1\">Bondy Connect, and Bondy (Lang) <a class=\"header-anchor\" href=\"#bondy-connect-and-bondy-lang\" aria-label=\"Permalink to &quot;Bondy Connect, and Bondy (Lang)&quot;\">&ZeroWidthSpace;</a></h2>\n<p>In its application networking platform years, Bondy was the connective tissue made visible. <a href=\"https://wamp-proto.org\" target=\"_blank\" rel=\"noreferrer\">WAMP</a> gave anything a common protocol to talk to anything, and clustering and replication let the whole thing survive a machine dying. We arranged all of it so the accidental complexity fell away and only the essential part remained.</p>\n<p>That role isn't going away, but the name is now expanding to cover the entire ambition. Meanwhile, the application networking platform itself is getting its own distinct name: <strong>Bondy Connect</strong>. Far from being retired or diminished, it is finally graduating to <code v-pre>v1.0.0</code>. For the first time, it is being named precisely. It is the mesh—the fabric that allows everything to communicate. It has earned its name, and it continues to do its job.</p>\n<p>And Bondy — just Bondy — is the brand and the philosophy, of which the language is the fullest expression. The attempt to take every idea I ever found beautiful in computer science and make them cohere into something you can actually program in. That includes the ones the market quietly left for dead.</p>\n<p>This isn't a rebrand in the marketing sense, where a logo changes and nothing else does. It's naming things correctly at last. The brand always meant more than an application networking platform. Giving it its own name lets Bondy finally stand for the whole of the ambition rather than one part of it.</p>\n<p>Consilience is not something you announce and then possess. It's something you build toward, imperfectly, for a long time, and Bondy has a long way still to go. But for the first time the name and the ambition point at the same thing.</p>\n<blockquote>\n<p>After decades of gluing, the seams are starting to disappear.\nBondy was a brand that wanted to be more than an application networking platform. It finally is.</p>\n</blockquote>\n<h2 id=\"what-it-looks-like\" tabindex=\"-1\">What it looks like <a class=\"header-anchor\" href=\"#what-it-looks-like\" aria-label=\"Permalink to &quot;What it looks like&quot;\">&ZeroWidthSpace;</a></h2>\n<p>I wrote the rest of the series for people who will type this in, so here is one module before the argument. It is a rate limiter that knows which features each plan includes. Every line compiles on the current toolchain, and every line is explained later in the series.</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Limiter</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> implements</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Event</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> | </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.{</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">NoReply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% -----------------------------------------------------</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Types</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% What a valid value looks like.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% -----------------------------------------------------</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Config</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">max_tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =&#x3C;</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 10000</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% -----------------------------------------------------</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Relations</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Facts you look up, and rules that derive more </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% of them.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% -----------------------------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Extends</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">parent</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Grants</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">  =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">feature</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">fact</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Extends</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"pro\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,  </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">parent</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"free\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">fact</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Grants</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"free\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">feature</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"api\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">fact</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Grants</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"pro\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">,  </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">feature</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#032F62;--shiki-dark:#A5D6FF\">\"webhooks\"</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Has</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">feature</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Grants</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">feature</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">feature</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">relation</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Has</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">feature</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">:-</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Extends</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">parent</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">p</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">    Has</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">p</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">feature</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">feature</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% -----------------------------------------------------</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Constraints and Rules</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Facts you impose on a store, rules that react, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% events that leave.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% -----------------------------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Bucket</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">constraint</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Request</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Int</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">event</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Denied</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> {</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> consume_token</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">      Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">      Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">      req</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">bucket</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    when</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">      t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> ></span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 0</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">      Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">      Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">remaining</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">t</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\"> 1</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">rule</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> deny</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    on</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">      Bucket</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">tokens</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">), </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">      Request</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    remove</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">      req</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    tell</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">      Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">id</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">rid</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">    end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% -----------------------------------------------------</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% Functions, with what they may do checked by the compiler.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%% -----------------------------------------------------</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">@law</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:pure</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79C0FF\">:deterministic</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> capacity</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">cfg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Config</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">::</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Int</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> -></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">  cfg</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.max_tokens</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> features</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">String</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span></span>\n<span class=\"line\"><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">    query</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Has</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">plan</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">feature</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">f</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> init</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_args</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">  Ok</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">([])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> e</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_ctx</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">  Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">e</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">as</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\"> e</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">_ctx</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">  Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">value</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">: </span><span style=\"--shiki-light:#E36209;--shiki-dark:#FFA657\">e</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">public function</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#D2A8FF\"> handle_event</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(_, _) </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">-></span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> </span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">  NoReply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">()</span></span></code></pre>\n</div><p>Two nouns the BEAM already gives you, functions and processes. Three that Bondy adds: the <a href=\"/glossary/shape.html\">shape</a>, which describes which values are allowed (<code v-pre>Config</code> above is a named one), the <a href=\"/glossary/rule.html\">rule</a>, and the <a href=\"/glossary/signal.html\">signal</a>. Four roles a value can play: <a href=\"/glossary/relation.html\">relation</a>, <a href=\"/glossary/constraint.html\">constraint</a>, <a href=\"/glossary/event.html\">event</a> and <a href=\"/glossary/fluent.html\">fluent</a>. Shapes, relations, constraints, events and both kinds of rule are in the compiler today, and so is the check on what every function is allowed to do. Fluents and signals are designed and next, and every post that touches them says so.</p>\n<h2 id=\"what-the-series-covers\" tabindex=\"-1\">What the series covers <a class=\"header-anchor\" href=\"#what-the-series-covers\" aria-label=\"Permalink to &quot;What the series covers&quot;\">&ZeroWidthSpace;</a></h2>\n<p>One post per idea, with code you can compile:</p>\n<ol>\n<li><strong>Bondy as Consilience</strong>, this post. The name, and why the application networking platform was never the whole ambition.</li>\n<li><strong>One noun, more roles</strong>. The thesis: a value can be held, imposed, emitted or latched, each role with its own verbs.</li>\n<li><strong>The type that validates</strong>. Shapes, refinements and unification: the check written once, run at compile time and at the boundary.</li>\n<li><strong>Rules that fire themselves</strong>. Rewrite rules over a store, replacing the state machine in your <code v-pre>handle_call</code>.</li>\n<li><strong>The process that owns the store</strong>. Events, and the supervised process that runs the rules.</li>\n<li><strong>Ask questions of your data</strong>. Relations and derivation rules, replacing the traversal you hand-roll.</li>\n<li><strong>What a function is allowed to do</strong>. Capabilities and laws: how the compiler knows what a function may do.</li>\n<li><strong>The alarm that cleared itself</strong>. Fluents and signals: designed, and next.</li>\n<li><strong>One module, five nouns</strong>. The whole rate limiter on one page, and how to get it.</li>\n</ol>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p><em>Declarative</em> is a spectrum, not a label, and BEAM programmers will rightly say that Erlang, Elixir and Gleam are declarative already. They are, in one sense: you write expressions over immutable values, not statements that change memory. But you still write the algorithm (which map to look in, which branch to take) and you still thread the state from one call to the next. Bondy keeps that functional core and goes further along the same line. With relations, rules and constraints you state the facts and what must hold, and the engine decides the order, the lookups and where the state lives. The <a href=\"/glossary/declarative.html\">glossary entry</a> has the longer version. <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p>The Meta Content Framework (MCF) is an early metadata specification developed by Ramanathan V. Guha at Apple Computer between 1995 and 1997 to structure information and relationships on the web. <a href=\"#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn3\" class=\"footnote-item\"><p>HotSauce, code-named Project X, was an experimental 3D web browser and plug-in. Apple Computer developed it in 1996 as a sample application of its Meta Content Framework (MCF). <a href=\"#fnref3\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn4\" class=\"footnote-item\"><p><a href=\"https://arxiv.org/abs/2204.07309\" target=\"_blank\" rel=\"noreferrer\">Saga: A Platform for Continuous Construction and Serving of Knowledge At Scale</a> <a href=\"#fnref4\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
            "url": "https://bondy.io/posts/2026-10-12-bondy-as-consilience",
            "title": "Bondy as Consilience",
            "summary": "<p><em>I'm publishing this series ahead of <a href=\"https://codebeameurope.com\" target=\"_blank\" rel=\"noreferrer\">Code BEAM Europe</a> this October, where I'll reveal Bondy, a new declarative[^declarative] language for the BEAM: statically typed, functional, relational and rule-based.</em></p>\n<p>Bondy is a brand about <em>connecting everything to everything</em>. Not only in the networking sense, but in a deeper one that has shaped how I've built software for as long as I've been building it. It is the conviction that many of the things we normally keep in separate boxes were always meant to belong together. Bondy is the name I gave to that conviction.</p>\n<p>It began as the name of our distributed application networking (DAN) platform. It removed the bureaucratic middleware the market kept throwing at us: the service mesh, the API gateway, the RPC framework, the pub/sub broker. Each solved a sliver of the same problem, and each demanded its own security, its own infrastructure, its own configuration, its own learning curve. Collapsing all of that into one coherent thing was the most pressing pain in front of us at <a href=\"https://www.leapsight.com\" target=\"_blank\" rel=\"noreferrer\">Leapsight</a>, so that is where I started. But the application networking platform was only ever one piece of Bondy.</p>\n<p>The ambition behind the brand was always larger: to take a great many beautiful and inspired ideas from computer science and bring them together into a single, coherent whole. The market neglected some of them for commercial rather than technical reasons. The application networking platform solved one piece of that puzzle. It was never the puzzle.</p>\n<MindMap mono=\"green\" title=\"Bondy (application networking platform)\n    a coherent whole\" \n  caption=\"The middleware tax — a half-dozen tools and protocols — collapsed into one tool and one protocol.\">\n<ul>\n<li>Service Mesh</li>\n<li>API Gateway</li>\n<li>Service Discovery Server</li>\n<li>Authentication Server</li>\n<li>Authorization Server</li>\n<li>RPC Framework</li>\n<li>Pub/Sub Broker</li>\n</ul>\n</MindMap>\n<h2 id=\"a-drawer-full-of-ideas-that-belonged-together\" tabindex=\"-1\">A drawer full of ideas that belonged together <a class=\"header-anchor\" href=\"#a-drawer-full-of-ideas-that-belonged-together\" aria-label=\"Permalink to &quot;A drawer full of ideas that belonged together&quot;\">&ZeroWidthSpace;</a></h2>\n<blockquote>\n<p>If you do this work long enough, you stop collecting tools and start collecting convictions.</p>\n</blockquote>\n<p>Mine accumulated slowly, one project at a time. By the late nineties they lived in an <a href=\"https://eastgate.com/Tinderbox/index.html\" target=\"_blank\" rel=\"noreferrer\">Eastgate Tinderbox</a> file — my intellectual drawers made digital — and it was filling up. OpenStep, distribution, tuplespaces, Lifestreams, Xanadu, JINI, Datalog, logic and constraint programming, Meta Content Framework and RDF, FIPA agents, Erlang. Notes linked to notes linked to notes, about systems that to everyone else lived in different fields and different conferences. But to me they kept pointing at the same underlying thing.</p>\n<p>Most of those ideas were not mine. They belonged to researchers and communities far smarter than me. What was mine was a stubborn feeling that they belonged together. They were not a handful of separate problems with separate solutions, but facets of one problem the world had never let sit in the same room.</p>\n<p>For a long while they stayed in separate drawers, because the industry insisted they belonged there, or because the best technology does not always win.</p>\n<p>I remember approaching one of the engineers behind the first version of OS X Spotlight, announced at Apple's WWDC in 2004. When I asked him why Spotlight wasn't built as an RDF store, he asked me what RDF was. This was shocking, given that RDF's ancestor—the Meta Content Framework (MCF)[^MCF]—was actually created at Apple and showcased by HotSauce[^Hotsauce]. At least now, almost 20 years later, RDF has finally found its way back to Apple[^SAGA].</p>\n<MindMap title=\"The drawers\" \n    links=\"Multiagent systems -- Actors; Linda/Tuplespaces -- JINI/Javaspaces;\n    Erlang -- Actors\" \n    caption=\"The separate drawers — and the one conviction underneath them.\">\n<ul>\n<li>Distributed Systems\n<ul>\n<li>Erlang</li>\n<li>JINI/Javaspaces</li>\n<li>CRDTs</li>\n<li>RPC</li>\n<li>Pub/Sub</li>\n</ul>\n</li>\n<li>Concurrent &amp; Parallel Programming\n<ul>\n<li>Actors</li>\n<li>Linda/Tuplespaces</li>\n</ul>\n</li>\n<li>Knowledge Representation\n<ul>\n<li>Meta Content Framework</li>\n<li>RDF/OWL\n<ul>\n<li>Knowledge Graphs</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>Data &amp; Knowledge Bases\n<ul>\n<li>Relational Model</li>\n<li>Bitemporal models</li>\n</ul>\n</li>\n<li>Dataflow Programming\n<ul>\n<li>Timely Dataflow</li>\n<li>Differential Dataflow</li>\n</ul>\n</li>\n<li>Symbolic AI, Logic &amp; Constraint programming\n<ul>\n<li>Prolog</li>\n<li>Datalog</li>\n<li>Constraint Handling Rules</li>\n<li>Neuro-symbolic Programming</li>\n</ul>\n</li>\n<li>Rule Systems\n<ul>\n<li>JESS</li>\n<li>CLIPS</li>\n</ul>\n</li>\n<li>Multiagent systems\n<ul>\n<li>FIPA</li>\n</ul>\n</li>\n<li>Interfaces\n<ul>\n<li>OpenStep</li>\n<li>OpenDocs</li>\n<li>Xanadu</li>\n<li>Lifestreams</li>\n</ul>\n</li>\n</ul>\n</MindMap>\n<h2 id=\"the-word-for-what-i-ve-been-doing\" tabindex=\"-1\">The word for what I've been doing <a class=\"header-anchor\" href=\"#the-word-for-what-i-ve-been-doing\" aria-label=\"Permalink to &quot;The word for what I've been doing&quot;\">&ZeroWidthSpace;</a></h2>\n<Quote author=\"E.O. Wilson\" source=\"Consilience: The Unity of knowledge\">\nWhen we have unified enough certain knowledge, we will understand who we are and why we are here. If those committed to the quest fail, they will be forgiven. When lost, they will find another way. The moral imperative of humanism is the endeavour alone, whether successful or not, provided the effort is honorable and failure memorable.\n</Quote>\n<BookCard cover=\"https://covers.openlibrary.org/b/isbn/9780349111124-M.jpg\" link=\"https://openlibrary.org/isbn/9780349111124\" title=\"Consilience\" subtitle=\"The Unity of Knowledge\" author=\"E. O. Wilson\" year=\"1998\" caption=\"The book that named it →\" /><p>I found the word for it in 1998, when I read <em>Consilience: The Unity of Knowledge</em> by E. O. Wilson. Wilson used it to mean the &quot;jumping together&quot; of knowledge — the way facts and theories from unrelated disciplines link up into a common groundwork of explanation. It is the opposite of specialisation: the belief that the seams between fields are artefacts of how we organised our universities, not features of reality.</p>\n<p>That named the drawer. What I'd been assembling wasn't a reading list or a wiki. It was a consilience waiting to happen.</p>\n<p>And that is what Bondy has always been reaching for. Not a product with a long feature list, but a consilience. There, the ideas I'd been carrying for decades could finally stop being neighbours and start being one thing.</p>\n<h2 id=\"why-the-whole-puzzle-needs-a-language\" tabindex=\"-1\">Why the whole puzzle needs a language <a class=\"header-anchor\" href=\"#why-the-whole-puzzle-needs-a-language\" aria-label=\"Permalink to &quot;Why the whole puzzle needs a language&quot;\">&ZeroWidthSpace;</a></h2>\n<blockquote>\n<p>The language is where the ambition was pointing the entire time.</p>\n</blockquote>\n<p>These ideas have to actually unify: not sit side by side, but compose, reinforce each other and become guarantees you can lean on. For that, they can't live in the plumbing. They have to live in the language you write your program in, and in the runtime that executes it.</p>\n<p>I want to be careful here, because it would be easy to read this as one thing turning into another: the application networking platform growing up into a language. It isn't that. Both are expressions of the same philosophy, and they solve different amounts of the puzzle. I began with the networking platform because I wasn't ready to solve the whole thing yet, and because the middleware tax was the pain right in front of us. What the language actually is — and how it makes these ideas cohere — is what the rest of this series is for.</p>\n<h2 id=\"why-now-we-need-a-declarative-language-more-than-ever\" tabindex=\"-1\">Why now: we need a declarative language more than ever <a class=\"header-anchor\" href=\"#why-now-we-need-a-declarative-language-more-than-ever\" aria-label=\"Permalink to &quot;Why now: we need a declarative language more than ever&quot;\">&ZeroWidthSpace;</a></h2>\n<p>There is a second reason, and it is newer than the drawer.</p>\n<p>Language models now write a growing share of software. That changes the economics of code, and it raises the stakes. For the systems that matter, the risk is no longer that the code cannot be produced. It is that nobody can understand, review or maintain what was produced, as it grows faster than anyone reads it.</p>\n<p>Some argue the opposite: once machines write the code, the language stops mattering. I think that gets it backwards. The less of the code a human writes, the more the language has to carry the meaning a human can check.</p>\n<p>A declarative program says <em>what</em> must be true, not the sequence of steps that happens to make it true today. It is mostly facts, rules and constraints, with little control flow in between, and that is what both people and models get right most often. A model is far more likely to get a rule, relation, constraint or type right than a long chain of imperative updates. Those updates must stay in sync across a dozen code paths. And a reviewer is far more likely to spot the one that is wrong.</p>\n<p>This is not a hypothesis. At Leapsight we see a reduction of 10x to 15x in lines of code compared to the Erlang that does the same job. The difference is the declarative part. The shapes, rules, relations and constraints that Erlang makes you spell out as control flow are one-line statements in Bondy. Less code is less to write, and above all less to review.</p>\n<p>The payoff is the same for people and for machines. Invariants live in one place, so programs are cheaper to change. Anything that can be worked out from other facts is written once as a rule, not kept in sync by hand, so there are fewer places for bugs to hide. And the program reads like its specification, so reviewing it means reading what it claims. Add a compiler that knows what every function is allowed to do, and code written by a tool can run with only the permissions you grant it. The <a href=\"./2026-10-12-what-a-function-is-allowed-to-do.html\">seventh post</a> is about that.</p>\n<p>The BEAM already gives us the operational foundation for systems that keep running. Bondy adds the semantic one. That matters most now that we write those systems together with machines.</p>\n<h2 id=\"bondy-connect-and-bondy-lang\" tabindex=\"-1\">Bondy Connect, and Bondy (Lang) <a class=\"header-anchor\" href=\"#bondy-connect-and-bondy-lang\" aria-label=\"Permalink to &quot;Bondy Connect, and Bondy (Lang)&quot;\">&ZeroWidthSpace;</a></h2>\n<p>In its application networking platform years, Bondy was the connective tissue made visible. <a href=\"https://wamp-proto.org\" target=\"_blank\" rel=\"noreferrer\">WAMP</a> gave anything a common protocol to talk to anything, and clustering and replication let the whole thing survive a machine dying. We arranged all of it so the accidental complexity fell away and only the essential part remained.</p>\n<p>That role isn't going away, but the name is now expanding to cover the entire ambition. Meanwhile, the application networking platform itself is getting its own distinct name: <strong>Bondy Connect</strong>. Far from being retired or diminished, it is finally graduating to <code v-pre>v1.0.0</code>. For the first time, it is being named precisely. It is the mesh—the fabric that allows everything to communicate. It has earned its name, and it continues to do its job.</p>\n<p>And Bondy — just Bondy — is the brand and the philosophy, of which the language is the fullest expression. The attempt to take every idea I ever found beautiful in computer science and make them cohere into something you can actually program in. That includes the ones the market quietly left for dead.</p>\n<p>This isn't a rebrand in the marketing sense, where a logo changes and nothing else does. It's naming things correctly at last. The brand always meant more than an application networking platform. Giving it its own name lets Bondy finally stand for the whole of the ambition rather than one part of it.</p>\n<p>Consilience is not something you announce and then possess. It's something you build toward, imperfectly, for a long time, and Bondy has a long way still to go. But for the first time the name and the ambition point at the same thing.</p>\n<blockquote>\n<p>After decades of gluing, the seams are starting to disappear.\nBondy was a brand that wanted to be more than an application networking platform. It finally is.</p>\n</blockquote>\n<h2 id=\"what-it-looks-like\" tabindex=\"-1\">What it looks like <a class=\"header-anchor\" href=\"#what-it-looks-like\" aria-label=\"Permalink to &quot;What it looks like&quot;\">&ZeroWidthSpace;</a></h2>\n<p>I wrote the rest of the series for people who will type this in, so here is one module before the argument. It is a rate limiter that knows which features each plan includes. Every line compiles on the current toolchain, and every line is explained later in the series.</p>\n<div class=\"language-bondy vp-adaptive-theme\"><button title=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">bondy</span><pre class=\"shiki shiki-themes github-light github-dark-default vp-code\" tabindex=\"0\" v-pre=\"\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">module</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Limiter</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> implements</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Event</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\"> =</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Allowed</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\"> | </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">Denied</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">import</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> GenReactor</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">.{</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#FF7B72\">type</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\"> Reply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">, </span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#FFA657\">NoReply</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#8B949E\">%%</span></span></code></pre>\n</div>",
            "date_modified": "2026-10-12T00:00:00.000Z",
            "author": {
                "name": "Alejandro M. Ramallo"
            },
            "tags": [
                "Language",
                "Bondy Connect",
                "MAS"
            ]
        }
    ]
}