lemma 0.11.3

A pure, declarative language for business rules.
Documentation
---
nav_title: Elixir
nav_order: 20
---

# Elixir

`lemma_engine` provides precompiled NIFs for Elixir (>= 1.14). Erlang and Gleam use the same package.

Precompiled binaries are downloaded automatically for macOS (arm64/x86_64), Linux (gnu x86_64 and arm64), and Windows (arm64/x86_64).

## Install

Add to `mix.exs`:

```elixir
def deps do
  [{:lemma_engine, "~> 0.10"}]
end
```

Or from git:

```elixir
{:lemma_engine, git: "https://github.com/lemma/lemma", sparse: "engine/packages/hex"}
```

## Usage

```elixir
{:ok, engine} = Lemma.new()

:ok = Lemma.load(engine, """
spec pricing
data quantity: number
data price: 10
rule total: quantity * price
rule discount: 0
  unless quantity >= 10 then 5
  unless quantity >= 50 then 15
""")

{:ok, response} = Lemma.evaluate(engine, %{spec: "pricing"}, %{data: %{"quantity" => 25}})
```

Integers (any size) and binaries are exact. Elixir floats and atoms other than `true` / `false` / `nil` return `{:error, %{kind: "request"}}` (`pass as string`). `nil` omits a map value; a `nil` list item is rejected. Measure data also accepts `%{"eur" => "84"}`. Keys differing only in case (including `"qty"` and `:qty`) are rejected.

A list is passed as an Elixir list that replaces every source row: binaries for a value list (`%{"fares" => ["89 eur", "120 eur"]}`), maps keyed by row data names for an instance list (`%{"staff" => [%{"name" => "Ada", "salary" => "9000 eur"}]}`), and/or dotted keys matching ask keys (`"staff.2.salary"`). In results, `Lemma.RuleResult.list` holds a list value's items. `Lemma.RuleResult.row` is a row reference whose `"index"` points into `Lemma.Response.rows`, where each referenced row appears once with its `"data"` and `"rules"`. In `Lemma.show/4`, an instance list with source rows has a `"value"` whose `"list"` items, in row order, carry only `"fields"`, the fields the row sets. Evaluation data for a slot bound by `uses -> with` (literal or reference) is a request error (sealed).

Introspect loaded Specs:

```elixir
{:ok, groups} = Lemma.specs(engine)
{:ok, show} = Lemma.show(engine, nil, "pricing")
{:ok, workspace_source} = Lemma.source(engine, nil, nil, nil)
{:ok, stdlib} = Lemma.source(engine, "lemma", nil, nil)
```

Format source code (no engine needed):

```elixir
{:ok, formatted} = Lemma.format("spec foo\ndata x: 1\nrule y: x + 1")
```

Install a repository from LemmaBase (download only; then `load`). Default transport is Req; pass a 2-arity fun for tests or custom HTTP:

```elixir
{:ok, result} = Lemma.install(engine, "@iso/countries")
:ok = Lemma.load(engine, [{result[:id], result[:source]}])
```

## API

| Function | Description |
|----------|-------------|
| `Lemma.version/0` | Semver of the loaded native engine (e.g. `"0.9.12"`) |
| `Lemma.new/1` | Create engine (optional limits map) |
| `Lemma.limits/1` | Current resource limits |
| `Lemma.load/2` | Load sources: binary (volatile); map (lexicographic label order) or `[{label, code}, ...]` (caller order) |
| `Lemma.install/2-3` | Download a repository from LemmaBase (`{:ok, map}` with `:source` / `:id`); optional `(url, headers) -> …` transport (default `Lemma.Transport.get/2`); does not load and does not write `lemma_deps/` |
| `Lemma.specs/1` | Loaded Specs grouped by repository (includes embedded `lemma` / `spec units`) |
| `Lemma.source/4` | Formatted Lemma source (`repository`, `spec`, `effective`; omit `spec` for repo-wide) |
| `Lemma.show/4` | Evaluation-data JSON Schema (Draft 2020-12) + this spec's own rules + temporal window (`repository`, `spec`, `effective`); Lemma metadata under `x-lemma-*`. `Lemma.Show.from_map/1` reads it: `types` is `x-lemma-types` (local declarations), `data` values are `Lemma.ShowData` (inputs rules need; full `type`, raw `schema`), `rows` are `Lemma.ShowRow`, `row_field_needed_by_rules` per row schema the row fields parent rules need through a list (`x-lemma-row-field-needed-by-rules`), `schema` is the document. `Show.data` / `Show.rules` carry `path` (`[]` = this spec). Branch Rule leaves may name imports outside `Show.rules`. |
| `Lemma.evaluate/3` | Evaluate: `target` map (`repo`, `spec`, `effective`), `options` map (`data`, `rules`, `explain`). Each rule result may include `missing_data` (unbound input keys). Non-veto results carry flattened `result` + typed keys (`Lemma.RuleResult`). With `explain: true`, `explanation` matches [api.v1.json](../../../engine/schemas/api.v1.json) (`RuleResult.explanation` / `ExplanationNode`). Types and suggestions are on `Lemma.show/4` only. |
| `Lemma.remove/4` | Remove temporal slice: `repository`, `spec`, `effective` |
| `Lemma.update/3-4` | Upsert identities from code; optional source `attribute` |
| `Lemma.quality/1` | Structural quality recommendations across loaded specs (advisory only) |
| `Lemma.format/1` | Format Lemma source code (no engine needed) |

`Lemma.Mcp` wraps the engine MCP catalog (`evaluate`, `specs`, `show`, `source`, `check`, `guide`, resources). `evaluate` returns formatted ASCII explanation trees (always on). `specs`, `show` and `check` return JSON; pass `"text" => true` for the CLI view. Write tools (`add_spec`, …) are CLI-only (`lemma mcp --write`).

`Lemma.OpenAPI` is a separate module (HTTP OpenAPI document helpers via `lemma_openapi`). It is not part of the core `Lemma` engine table above.

## Engine lifecycle

Each `Lemma.new/1` call creates an independent engine. The engine reference is safe to use from a single process. For shared access across processes, wrap it in a GenServer.