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 Prolog 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 partial goal term (a cell such as add(1), which call extends with the extra arguments).
Mapping¶
maplist/2¶
maplist(Goal, List) — test Goal(Elem) for every element. Succeeds if the
goal succeeds for all elements, and backtracks into every call, as the ISO
prologue's definition by call/N does: with p(1), p(2), p(3),
maplist(p, [X, Y]) has nine answers. On an OPEN list (unbound, or a
partial list [a, *T]) it enumerates as that definition does, depth first
and without end: maplist(p, L) answers L = [], L = [1], L = [1, 1],
...
maplist/3¶
maplist(Goal, Xs, Ys) — transform each element via Goal(X, Y); like
maplist/2 it backtracks into every call.
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, eval_(X ** 0.5, Y))
test("filtermap") <- filter_map(safe_sqrt, [4, -1, 9, -2, 16], [2.0, 3.0, 4.0])
Folding¶
foldl/4, foldl/5, foldl/6¶
foldl(Goal, List, V0, V) — left fold. Applies Goal(Elem, Acc, NewAcc)
across the list, threading an accumulator from V0 to V.
foldl(Goal, Xs, Ys, V0, V) calls Goal(X, Y, Acc, NewAcc) over two lists
of one length, and foldl/6 over three, as Scryer's library(lists).
Like maplist, foldl backtracks into every call (each solution of the goal
gives another fold), and an open list enumerates: foldl(G, L, 0, S) with
L unbound answers L = [] first, then longer lists.
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 is ++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 is ++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). ^is not a goal —Y^Goalmeans something only as bagof/setof's goal argument;call(Y^p(Y))isexistence_error(procedure, (^)/2), as in ISO and Scryer.group_bygroups consecutive runs — not global grouping. sort first if needed.include/excludeare committed-choice — they test each element once (first solution only) and do not backtrack into the goal.maplistdoes.- 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.