Higher-Order Predicates¶
Higher-order predicates take a goal (predicate or lambda) as an argument and apply it to list elements. Combined with lambdas, they give Clausal a functional programming feel.
Quick Example¶
double(X, Y) <- (Y == X * 2)
Test("double all") <- (
maplist(double, [1, 2, 3], [2, 4, 6])
)
Test("keep evens") <- (
include((X <- (X % 2 == 0)), [1, 2, 3, 4, 5, 6], [2, 4, 6])
)
Calling Goals¶
call/1..8 and call_goal/1..8¶
Call(Goal) invokes a goal. Call(Goal, A1, ..., AN) appends extra arguments.
call_goal is an alias.
Test("call/1") <- Call((X <- (X == 42)), 42)
Test("call/2") <- (
Call(((X, Y) <- (Y == X * 2)), 5, 10)
)
The goal can be a lambda, a predicate name, or a predicate instance.
Mapping¶
maplist/2¶
maplist(Goal, List) — test Goal(Elem) for every element. Succeeds if the
goal succeeds for all elements.
maplist/3¶
maplist(Goal, Xs, Ys) — transform each element via Goal(X, Y).
With an inline lambda:
Filtering¶
include/3¶
include(Goal, List, Included) — keep elements where Goal(Elem) succeeds.
exclude/3¶
exclude(Goal, List, Kept) — keep elements where Goal(Elem) fails.
The inverse of include.
filter_map/3¶
filter_map(Goal, List, Result) — map and filter in one pass. Keep the output
value when Goal(Elem, Out) succeeds; skip elements where it fails.
safe_sqrt(X, Y) <- (X >= 0, Y == X ** 0.5)
Test("filtermap") <- filter_map(safe_sqrt, [4, -1, 9, -2, 16], [2.0, 3.0, 4.0])
Folding¶
foldl/4¶
foldl(Goal, List, V0, V) — left fold. Applies Goal(Elem, Acc, NewAcc)
across the list, threading an accumulator from V0 to V.
With an inline lambda:
Prefix & Suffix¶
take_while/3¶
take_while(Goal, List, Prefix) — longest prefix where Goal(Elem) succeeds.
drop_while/3¶
drop_while(Goal, List, Suffix) — drop the prefix where Goal(Elem) succeeds.
span/4¶
span(Goal, List, Yes, No) — take_while + drop_while in one pass.
Grouping & Sorting¶
group_by/3¶
group_by(Goal, List, Groups) — group consecutive elements by key.
Goal(Elem, Key) extracts the grouping key.
first_char(S, C) <- (C := ++S[0])
Test("group by first char") <- (
group_by(first_char, ["apple", "avocado", "banana", "blueberry", "cherry"], [["apple", "avocado"], ["banana", "blueberry"], ["cherry"]])
)
Note: groups are consecutive runs, not global grouping. sort first if you need global groups.
sort_by/3¶
sort_by(Goal, List, Sorted) — sort by projected key.
abs_key(X, K) <- (abs_(X, K))
Test("sort by abs") <- (
sort_by(abs_key, [3, -1, -4, 2], [-1, 2, 3, -4])
)
max_by/3, min_by/3¶
max_by(Goal, List, max_) / min_by(Goal, List, min_) — element with the
largest/smallest projected key.
str_len(S, K) <- (K := ++len(S)) # see [Python interop](python_integration.md)
Test("longest") <- (
max_by(str_len, ["hi", "hello", "hey"], "hello")
)
Patterns & Recipes¶
Map then filter (pipeline)¶
sq(X, Y) <- (Y == X * X)
Test("pipeline") <- (
maplist(sq, [1, 2, 3, 4, 5], SQUARES),
include((X <- (X > 10)), SQUARES, BIG),
BIG == [16, 25]
)
flatten via foldl¶
Test("flat") <- (
foldl(((CHUNK, ACC, OUT) <- append(ACC, CHUNK, OUT)),
[[1, 2], [3], [4, 5]],
[],
[1, 2, 3, 4, 5])
)
Count occurrences¶
count(PRED, LIST, N) <- (
include(PRED, LIST, MATCHED),
length(MATCHED, N)
)
Test("count evens") <- count((X <- (X % 2 == 0)), [1, 2, 3, 4, 5, 6], 3)
Gotchas¶
- Goal argument order matters —
maplist/3callsGoal(X, Y)where X is input, Y is output.foldl/4callsGoal(Elem, AccIn, AccOut). group_bygroups consecutive runs — not global grouping. sort first if needed.- Lambdas are committed-choice —
includetests each element once (first solution only). It does not backtrack into the goal. - Lambda syntax — single-arg:
(X <- (body)). Multi-arg:((X, Y) <- (body))with extra outer parens for the tuple. See Lambdas for details. Call/Nappends arguments —Call(foo, 1, 2)callsfoo(1, 2).
See also: Lambdas — goal closure syntax, Meta-Predicates — findall, bagof, setof, Lists — list operations.