The standard library
The std. namespace (File structure and attributes) gathers
list and option combinators, associative structures, and a few
overridable hooks. It is written in Elpi, and its full signatures are printed
by elpi -document-builtins (or read straight from src/builtin.elpi). What
those signatures do not tell you is what follows: the naming conventions,
and which structure to reach for.
Naming conventions
A suffix on a combinator’s name says how it varies from the plain form:
!: a cut is baked in, so the call commits to its first solution.std.mem!beside the backtrackingstd.mem,std.lookup!besidestd.lookup.2: walks two lists in lockstep, raising a fatal error if their lengths differ.std.map2,std.fold2,std.forall2,std.exists2.-i: the callback also receives the element’s 0-based index.std.map-i,std.list.init.-filter: the callback may fail, and where it does the element is dropped.std.map-filteris map and filter in one pass.-ok: the callback’s last output is adiagnostic; the firsterrorstops the traversal and is returned.std.map-ok,std.forall-ok,std.while-ok-do!.R: a relation, with every argument output-mode, so the predicate runs in any direction. Only one predicate has it,std.appendR:std.appendR X Y [1,2,3]enumerates every way to split the list, not only the one whereXandYare already known.
Two hooks are ordinary named rules (Rule attributes) that a
program can graft over to change how the library behaves. Every error the
library raises goes through std.fatal-error / std.fatal-error-w-data,
and every debug line through std.debug-print; grafting a rule
:before "default-fatal-error" turns a library error into something the
host catches instead of a halt.
List and option combinators
std.map, std.filter, std.fold, std.exists, std.forall,
std.mem, std.append, std.rev, std.length, std.nth,
std.take / std.drop, std.zip / std.unzip, std.iota and
their relatives mirror what a functional language’s list module offers; the
suffix conventions above generate the rest of the family from each base name.
std.omap is the option counterpart of std.map.
code/stdlib-tour.elpi:
1% Suffix conventions of the std. library: -i passes the index, -filter drops
2% where the callback fails, 2 walks two lists in lockstep. Then a comparator
3% map (any key type), and the one R-suffixed relation, std.appendR.
4
5main :-
6 std.map-i [10, 20, 30] (i\ x\ y\ y is x + i) R1,
7 print "map-i (index added):" R1,
8
9 std.map-filter [1, 2, 3, 4, 5] (x\ y\ 0 is x mod 2, y = x) Evens,
10 print "map-filter (evens kept):" Evens,
11
12 std.fold2 [1, 2, 3] [10, 20, 30] 0 (a\ b\ acc\ acc'\ acc' is acc + a * b) Dot,
13 print "fold2 (dot product):" Dot,
14
15 std.map.make cmp_term M0,
16 std.map.add "x" 1 M0 M1,
17 std.map.find "x" M1 V,
18 print "std.map, x ->" V,
19
20 ( std.appendR A B [1, 2], print "appendR split:" A B, fail ; true ).
map-i (index added): [10, 21, 32]
map-filter (evens kept): [2, 4]
fold2 (dot product): 140
std.map, x -> 1
appendR split: [] [1, 2]
appendR split: [1] [2]
appendR split: [1, 2] []
Associative structures
Four map/set families, differing in what keys they take and how they are implemented:
std.mapandstd.settake any key type, given a comparatorfunc K, K -> cmppassed to.make. They are balanced search trees written in Elpi, so a key may contain unification variables.std.fmapandstd.fsetare the same trees, but skip the occur check for a speed gain. They require every key to be a ground term and raise a fatal error otherwise.std.string.map,std.int.map,std.loc.map(andstd.string.set/std.int.set) are the FFI-backed structures for one fixed key type, backed by OCaml’s own maps (Built-in predicates).
std.map shares its name with the list-mapping combinator; a predicate and
a type live in different namespaces, so this is unambiguous but easy to
misread.