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).