Current section
Files
Jump to
Current section
Files
notebooks/quickstart.livemd
<!-- livebook:{"persist_outputs":true} -->
# ExMaude Quick Start
```elixir
app_root = Path.expand("..", __DIR__)
if File.exists?(Path.join(app_root, "mix.exs")) do
# Running from a clone of the repository: use the local checkout,
# which bundles the Maude interpreter under priv/.
Mix.install(
[{:ex_maude, path: app_root, env: :dev}],
config_path: :ex_maude,
lockfile: :ex_maude,
config: [ex_maude: [use_pty: false]]
)
else
# Running standalone (e.g. via the "Run in Livebook" badge): use the
# released package. This needs a `maude` binary on your PATH, or the
# MAUDE_PATH env var set — see https://github.com/futhr/ex_maude#installation
Mix.install(
[{:ex_maude, "~> 0.4"}],
config: [ex_maude: [use_pty: false]]
)
end
{:ok, _pool_supervisor} =
Supervisor.start_link([ExMaude.Pool.child_spec()], strategy: :one_for_one)
```
## Introduction
[ExMaude](https://github.com/futhr/ex_maude) is an Elixir client for [Maude](https://maude.cs.illinois.edu/), a language and system for *rewriting logic*. Where Elixir programs describe **how** to compute, a Maude specification describes **what is true** about a system: which expressions are equal, and which state transitions are possible. Maude can then compute with those facts — simplifying expressions, running state machines, and exhaustively exploring every reachable state to prove properties.
That last part is the superpower. Maude excels at:
* **Term rewriting** — reducing expressions to their simplest form
* **Equational reasoning** — computing with algebraic laws instead of hand-written algorithms
* **State space exploration** — searching every possible execution of a system
* **Formal verification** — proving that bad states can never be reached
This notebook covers the basic ExMaude API. The [Term Rewriting](rewriting.livemd) notebook then builds up the Maude concepts step by step, and [Advanced Usage](advanced.livemd) applies them to real rule-conflict detection.
> **Running this notebook** — the easiest path is to open it from a clone of the [ex_maude repository](https://github.com/futhr/ex_maude), where the Maude interpreter is bundled and nothing else needs installing. Opened standalone (for example via the *Run in Livebook* badge), the setup cell falls back to the released package, which needs a `maude` binary on your `PATH` — see the [installation guide](https://github.com/futhr/ex_maude#installation).
## Your First Reduction
Maude code lives in **modules** — named collections of types (*sorts*), operators, and laws. Maude ships with built-in modules like `NAT` (natural numbers), `INT` (integers), and `STRING`.
The most fundamental operation is `reduce`: give Maude a module and a **term** (an expression), and it applies the module's equations until nothing more can change. The stable result is called the **normal form**.
```elixir
ExMaude.reduce("NAT", "1 + 1")
```
Every ExMaude call returns `{:ok, result}` or `{:error, reason}` — the result of a reduce is the normal form as a string:
```elixir
ExMaude.reduce("NAT", "2 * 3 + 4")
```
Other built-in modules work the same way. `INT` adds negative numbers:
```elixir
ExMaude.reduce("INT", "-5 + 10")
```
Even large computations are fast — Maude is a mature interpreter with decades of optimization behind it:
```elixir
ExMaude.reduce("NAT", "2 ^ 64")
```
## Parsing Without Evaluating
`parse` checks that a term is syntactically valid in a module — without evaluating it. Maude answers with the term's most specific **sort** (its inferred type):
```elixir
ExMaude.parse("NAT", "1 + 2 * 3")
```
The `NzNat:` prefix is Maude telling you the term's sort is *non-zero natural number* — inferred from the operators alone, before any evaluation. Invalid terms return an error that pinpoints the bad token:
```elixir
ExMaude.parse("NAT", "invalid syntax here")
```
## Equations vs Rules: a Teaser
Maude has two kinds of laws. **Equations** state that two things are equal — `reduce` applies them. **Rules** state that one state can *transition* to another — `rewrite` applies those. Here is a two-state system, loaded from a string:
```elixir
coffee = """
mod COFFEE is
sort State .
ops tired caffeinated : -> State [ctor] .
rl [drink] : tired => caffeinated .
endm
"""
ExMaude.load_module(coffee)
```
The rule `[drink]` says a `tired` state can become `caffeinated`. `rewrite` runs the transition:
```elixir
ExMaude.rewrite("COFFEE", "tired")
```
State transitions are where Maude gets interesting — the [Term Rewriting](rewriting.livemd) notebook explores them properly, including how to *search* every reachable state.
## Working with Strings
Maude has a built-in `STRING` module. Maude string literals use double quotes, so the examples below use Elixir's `~s{...}` sigil to avoid escaping. (Note the `{}` delimiters — with `~s(...)`, the parentheses inside the Maude expression would end the sigil early.)
```elixir
ExMaude.reduce("STRING", ~s{"Hello" + " " + "World"})
```
```elixir
ExMaude.reduce("STRING", ~s{length("ExMaude")})
```
```elixir
ExMaude.reduce("STRING", ~s{substr("Hello World", 0, 5)})
```
## Error Handling
Everything returns tagged tuples, so the usual Elixir patterns apply:
```elixir
case ExMaude.reduce("NAT", "1 + 1") do
{:ok, result} -> "Result: #{result}"
{:error, error} -> "Error: #{inspect(error)}"
end
```
Referencing a module that isn't loaded:
```elixir
ExMaude.reduce("NONEXISTENT-MODULE", "1 + 1")
```
A syntax error inside a term:
```elixir
ExMaude.reduce("NAT", "1 + + 1")
```
## Inspecting Modules
To see what a module actually contains, ask for its definition. Here is the real `NAT` — note the `op` declarations and their attributes:
```elixir
ExMaude.show_module("NAT")
```
List everything currently loaded (your `COFFEE` module is in there now):
```elixir
ExMaude.list_modules()
```
And for anything the high-level API doesn't cover, send raw Maude commands directly — note the trailing ` .` that ends every Maude command:
```elixir
ExMaude.execute("reduce in NAT : 10 * 10 .")
```
## Under the Hood
ExMaude runs Maude as a pool of separate OS processes, so a Maude crash can never take down the BEAM, and concurrent callers each get their own interpreter:
```elixir
ExMaude.Pool.status()
```
```elixir
ExMaude.version()
```
## Where to Next
Follow the notebooks in this order for a gradual introduction:
1. [Term Rewriting](rewriting.livemd) — equations vs rules, writing your own modules, and `search`: the exhaustive state exploration that makes verification possible
2. [Advanced Usage](advanced.livemd) — custom modules in practice, IoT rule-conflict detection, pooling, telemetry
3. [AI Rules](ai-rules.livemd) — conflict detection for AI agent policies: capabilities, approval gates, sovereignty
4. [Benchmarks](benchmarks.livemd) — what performance to expect, and how verification cost scales