From 65ecc1d4f1f68230a11987a43d7f53841d21d375 Mon Sep 17 00:00:00 2001 From: JohnnyT Date: Mon, 5 Oct 2026 05:32:52 -0600 Subject: [PATCH] Adds a guide to adding host functions A how-to page under docs/guides: write a call into a rule, supply the function under the functions option of evaluate, and handle a call that fails (a function left out, a function that throws, an answer outside the value domain), with the result each step shows. The README's Documentation map links it under Do, and the Host functions section's line moves to Look up, where its reference text belongs. Refs: pts-8f7z --- README.md | 3 +- docs/guides/how-to-add-host-functions.md | 90 ++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 docs/guides/how-to-add-host-functions.md diff --git a/README.md b/README.md index 2af0f79..3d837d8 100644 --- a/README.md +++ b/README.md @@ -78,12 +78,13 @@ failing arm of the result rather than as a throw; `ok` tells the two apart. - [Rendering a rule back](#rendering-a-rule-back): show an authored rule back to its author in a normalized form. - [Statement programs](#statement-programs): run a short script that assigns into its context. - [Writing a location](#writing-a-location): write an assignment's location into your own data without running a program. - - [Host functions](#host-functions): let a rule call functions your host supplies. + - [How to add host functions](https://github.com/riddler/predicator-ts/blob/main/docs/guides/how-to-add-host-functions.md): let a rule call a function your application supplies, and handle a call that fails. - Look up - The API reference: every exported function and type, built into `docs/api/` by `mise exec -- pnpm run docs` in a clone of this repository. - [The entry points](#the-entry-points): the two import paths and what each one exports. - [The value domain](#the-value-domain): each value type and its shape in TypeScript. - [Evaluation options](#evaluation-options): the options an evaluation takes, and their defaults. + - [Host functions](#host-functions): how a host function's arguments and answer cross into the language's values, and how its name shadows a builtin. - [The tagged subpath](#the-tagged-subpath): the encoding that keeps what a JSON round trip loses. - [The changelog](https://github.com/riddler/predicator-ts/blob/main/CHANGELOG.md): what changed in each version. - Understand diff --git a/docs/guides/how-to-add-host-functions.md b/docs/guides/how-to-add-host-functions.md new file mode 100644 index 0000000..7dd69a2 --- /dev/null +++ b/docs/guides/how-to-add-host-functions.md @@ -0,0 +1,90 @@ +# How to add host functions + +This guide shows you how to let a rule call a function your application +supplies, so the rule can ask about data the context does not carry. + +You start with `@riddler/predicator` installed and a renewal rule that needs +your application's hold queue: a loan may be renewed when no other patron is +waiting for the copy and the loan has been renewed fewer than twice. + +## Call the function from the rule + +1. Write the call into the rule's text, with the function's name and its + arguments, and compile it. + + ```ts + import { compile } from "@riddler/predicator"; + + const compiled = compile("holds_waiting(copy.id) == 0 AND loan.renewals < 2"); + + if (!compiled.ok) { + throw new Error(compiled.error.message); + } + ``` + + `compiled.instructions` holds `["call", "holds_waiting", 1]`, a call with one + argument. + +## Supply the function + +1. Write the function. It takes the call's arguments as an array and answers + one value. + + ```ts + import type { HostFunction } from "@riddler/predicator"; + + const holdQueues: Record = { + "atlas-2": ["patron-17"], + "codex-1": [], + }; + + const holdsWaiting: HostFunction = ([copyId]) => + typeof copyId === "string" ? (holdQueues[copyId] ?? []).length : 0; + ``` + + `holdsWaiting(["atlas-2"])` answers `1`. + +2. Pass it to `evaluate` under `functions`, keyed by the name the rule calls. + + ```ts + import { evaluate } from "@riddler/predicator"; + + const renewable = evaluate( + compiled.instructions, + { copy: { id: "codex-1" }, loan: { renewals: 1 } }, + { functions: { holds_waiting: holdsWaiting } }, + ); + ``` + + `renewable` is `{ ok: true, value: true }`: nobody is waiting for this copy. + +3. Evaluate it for a copy another patron is waiting for. + + ```ts + const held = evaluate( + compiled.instructions, + { copy: { id: "atlas-2" }, loan: { renewals: 1 } }, + { functions: { holds_waiting: holdsWaiting } }, + ); + ``` + + `held` is `{ ok: true, value: false }`. + +## Handle a call that fails + +1. Check `ok` before you read `value`. If you leave `functions` out, `ok` is + `false` and `error.reason` is `"Unknown function: holds_waiting"`. + +2. If your function throws, read its message from the result. For a function + that throws `new Error("the hold queue is unavailable")`, `ok` is `false`, + `error.type` is `"EvaluationError"` and `error.reason` is + `"the hold queue is unavailable"`. + +3. If your function answers a value the language has no type for, such as a + symbol, `ok` is `false` and `error.reason` is `"unsupported_host_value"`. + +For how arguments and answers cross into the language's values, and how a +host function's name shadows a builtin of the same name, see +[Host functions](https://github.com/riddler/predicator-ts/blob/main/README.md#host-functions) +and +[The value domain](https://github.com/riddler/predicator-ts/blob/main/README.md#the-value-domain).