Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
90 changes: 90 additions & 0 deletions docs/guides/how-to-add-host-functions.md
Original file line number Diff line number Diff line change
@@ -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<string, string[]> = {
"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).
Loading