Meta-Predicates & Higher-Order¶
Clausal provides meta-predicates for collecting solutions and higher-order list predicates for functional-style list processing. Meta-predicates are compiler special forms (compiled inline); higher-order list predicates are builtins that take goal closures.
Meta-Predicates (Compiler Special Forms)¶
These are compiled inline by the compiler — they are not dispatched as builtins.
findall/3¶
findall(Template, Goal, Bag) — collect all instances of Template for which Goal succeeds.
If Goal has no solutions, Bag is unified with [].
bagof/3¶
bagof(Template, Goal, Bag) — like findall, but fails if Goal has no solutions (findall returns [] instead).
bagof vs findall¶
The only difference between bagof and findall is how they handle the empty case:
age("alice", 30),
age("bob", 25),
age("carol", 30),
all_people(PEOPLE) <- bagof(NAME, age(NAME, _), PEOPLE)
Test("bag of people") <- (
all_people(PEOPLE),
length(PEOPLE, 3)
)
Use bagof when you want failure on empty results, findall when you always want a list.
setof/3¶
setof(Template, Goal, Set) — like bagof, but returns a sorted list with duplicates removed. Also fails on no solutions.
setof vs sort(findall(...))¶
Both produce sorted, deduplicated results. The differences:
setof(T, G, S) |
findall(T, G, S0) + sort(S0, S) |
|
|---|---|---|
| Empty result | Fails | Succeeds with S = [] |
| Deduplication | Yes (sorted set) | Only if you call sort |
| Use when | You want failure on empty | You always want a list |
Test("setof unique") <- (
setof(X, in_(X, [3, 1, 2, 1, 3]), XS),
length(XS, 3)
)
# setof fails if no solutions; findall always succeeds (returns []):
Test("findall empty ok") <- (
findall(X, (in_(X, []), X > 0), BAG),
BAG == []
)
forall/2¶
forall(Condition, Action) — succeeds if for every solution of Condition, Action also succeeds. equivalent to not (Condition, not Action).
forall Patterns¶
Validate all elements:
Check a property across a collection:
all_connected(NODES, GRAPH) <- (
forall(
(in_(A, NODES), in_(B, NODES), dif(A, B)), # dif — see [Constraints](constraints.md)
reachable(A, B, GRAPH)
)
)
Guard before processing:
Call/N¶
call/1..8 invokes a goal closure with 0–7 extra arguments. call_goal/1..8 are aliases.
These are primarily used with lambdas:
Higher-Order List Predicates¶
These builtins take a goal as their first argument — either a lambda (goal closure) or a predicate reference (builtin or user-defined). All use committed choice — they take the first solution from the goal for each element. See Higher-Order for the dedicated reference page.
maplist/2¶
maplist(Goal, List) — succeeds if Goal succeeds for every element of List.
# With a lambda
all_positive(XS) <- maplist((X <- (X > 0)), XS)
# With a builtin predicate
all_numbers(XS) <- maplist(number, XS)
maplist/3¶
maplist(Goal, List, ResultList) — apply a binary goal to each element, collecting results.
include/3¶
include(Goal, List, Filtered) — keep elements for which Goal succeeds.
# With a lambda
positives(XS, PS) <- include((X <- (X > 0)), XS, PS)
# With a builtin predicate
keep_numbers(XS, NS) <- include(number, XS, NS)
exclude/3¶
exclude(Goal, List, Remaining) — keep elements for which Goal fails (complement of include).
foldl/4¶
foldl(Goal, List, Acc0, Result) — left fold with a ternary goal closure.
fold_sum(XS, S) <- foldl(((ELEM, ACC, R) <- (R == ACC + ELEM)), XS, 0, S)
fold_product(XS, P) <- foldl(((ELEM, ACC, R) <- (R == ACC * ELEM)), XS, 1, P)
take_while/3¶
take_while(Goal, List, Prefix) — longest prefix where Goal succeeds for each consecutive element.
drop_while/3¶
drop_while(Goal, List, Suffix) — suffix after dropping the longest prefix where Goal succeeds.
span/4¶
span(Goal, List, Yes, No) — take_while + drop_while in one pass.
group_by/3¶
group_by(Goal, List, Groups) — group consecutive elements by key projected via Goal(Elem, Key).
sort_by/3¶
sort_by(Goal, List, Sorted) — sort by key projected via Goal(Elem, Key). Stable sort.
max_by/3, min_by/3¶
max_by(Goal, List, max_) / min_by(Goal, List, min_) — element with largest/smallest key. Fails on empty list.
filter_map/3¶
filter_map(Goal, List, Result) — map + filter in one pass. Calls Goal(Elem, Out) for each element; keeps Out when goal succeeds, skips when it fails.
Additional List Predicates¶
| Builtin | Arity | Description |
|---|---|---|
pairs_keys_values |
3 | pairs_keys_values(Pairs, Keys, Values) — split list of pairs |
pairs_keys |
2 | pairs_keys(Pairs, Keys) — extract keys from pairs |
pairs_values |
2 | pairs_values(Pairs, Values) — extract values from pairs |
Combining Meta-Predicates with Lambdas
Meta-predicates take inline goal expressions (not closures), so lambdas aren't needed:
# findall with inline goal — no lambda required
squares(NS, SQS) <- findall(SQ, (in_(X, NS), SQ == X * X), SQS)
# forall with inline condition and action
all_positive(NS) <- forall(in_(X, NS), X > 0)
Higher-order list predicates take either lambdas or predicate references:
# include with lambda
positives(XS, PS) <- include((X <- (X > 0)), XS, PS)
# include with a builtin predicate directly
keep_ints(XS, IS) <- include(integer, XS, IS)
See Lambdas for full lambda syntax and semantics.
Test coverage
tests/test_meta.py(23 tests): findall, bagof, setof, forall, Call/N,.clausalintegrationtests/test_higher_order.py(34 tests): maplist/2,3, include/3, exclude/3, foldl/4, builtin predicates as argumentstests/fixtures/builtin_as_arg.clausal(5 tests): include/maplist with builtin predicates (number, integer, succ)
See also: Lambdas — goal closures used with higher-order predicates, Lists — list predicates that pair well with meta-predicates, Higher-Order — dedicated page for maplist, include, foldl, etc.