Goal-Oriented Programming: Backward Chaining + GOAP
A worked example of PatLang's goal-oriented paradigm, run live below โ see the Paradigms Guide for how rule_add/solve and action_add/plan fit together, the Grammar reference for the declarative rule ... :- ... syntax this demo could equally be written in, and the Standard Library reference for the full function list.
PatLang's goal-oriented paradigm is genuinely real, not a facade: rule_add/solve do actual SLD-style backward-chaining resolution with backtracking (a ground fact is just a rule with an empty body โ the standard Prolog trick), and action_add/plan is a real GOAP planner, doing uniform-cost forward search over actions with preconditions/effects/cost to find the cheapest ordered plan โ not just picking the first path that works. A successful require/ensure check also asserts a contract_holds fact, so design-by-contract checks feed directly into either engine. This demo derives whether a build target is buildable via a genuine multi-hop dependency chain, and separately produces a real 3-step build plan that correctly prefers a cheaper path over a pricier shortcut.
PatLang source
# A genuine goal-oriented build-system demo -- proving PatLang's
# backward-chaining resolver (A1), GOAP planner (A2), and design-by-contract
# both work AND compose, unlike the old fact/goal/query trio (still present,
# unchanged, for backward compatibility) which was flat and write-only.
#
# A1 answers "is target X buildable" by deriving it from dependency/changed
# facts through real multi-hop rule chaining and backtracking -- not a
# single-predicate lookup.
# A2 answers "what's the actual build plan" -- an ordered, cost-aware
# sequence of actions to reach a goal state, the natural shape of a real
# build's output.
# A DbC contract's successful check becomes a fact usable by either.
# ---- dependency graph: a -> b -> c (c is unchanged; a and b are not) ----
rule_add("dep", ["a", "b"], [])
rule_add("dep", ["b", "c"], [])
rule_add("unchanged", ["c"], [])
# buildable(X) :- unchanged(X).
# buildable(X) :- dep(X, Y), buildable(Y).
# A target is buildable if it's unchanged itself, or everything it
# transitively depends on is -- real multi-hop backward chaining, not a
# flat lookup: proving "a" buildable requires walking a -> b -> c.
rule_add("buildable", ["X"], [["unchanged", ["X"]]])
rule_add("buildable", ["X"], [["dep", ["X", "Y"]], ["buildable", ["Y"]]])
let sols_a = solve("buildable", ["a"])
print("buildable(a) via a->b->c, c unchanged: " + list_len(sols_a) + " solution(s)")
# A genuinely unbuildable case: "d" depends on "e", which is never declared
# unchanged anywhere -- the resolver must actually search and correctly fail,
# not just return the first thing that looks plausible.
rule_add("dep", ["d", "e"], [])
let sols_d = solve("buildable", ["d"])
print("buildable(d) via d->e, e never unchanged: " + list_len(sols_d) + " solution(s) (should be 0)")
# ---- design-by-contract: verify the resolver itself, contract becomes a fact ----
make a function called hop_count takes chain_len returns result
require chain_len >= 0
let result = chain_len
ensure result >= 0
return result
end
let hc = hop_count(2)
let contract_sols = solve("contract_holds", ["hop_count", "X"])
print("contract_holds facts recorded for hop_count: " + list_len(contract_sols))
# ---- A2: GOAP planner produces the actual ordered, cost-aware build plan ----
action_add("compile_b", [], [["compiled", ["b"]]], [], 5)
action_add("compile_a", [["compiled", ["b"]]], [["compiled", ["a"]]], [], 3)
action_add("link", [["compiled", ["a"]], ["compiled", ["b"]]], [["shipped", []]], [], 2)
# A pricier shortcut that skips straight to "shipped" without building
# anything -- the planner must find the CHEAPER real path (5+3+2=10) is
# actually the only valid one here since this shortcut needs "compiled b"
# too, proving cost-based search isn't just picking the first match.
action_add("direct_ship", [["compiled", ["b"]]], [["shipped", []]], [], 100)
let build_plan = plan([["shipped", []]])
print("build plan (should be the 3-step compile_b/compile_a/link path, not the pricier shortcut): " + list_len(build_plan) + " step(s)")
let mut i = 0
let n = to_num(list_len(build_plan))
while i < n do
print(" step " + (i + 1) + ": " + build_plan[i])
i = i + 1
end
print("DONE")
(not run yet)
Native run on the build machine:
buildable(a) via a->b->c, c unchanged: 1 solution(s) buildable(d) via d->e, e never unchanged: 0 solution(s) (should be 0) contract_holds facts recorded for hop_count: 2 build plan (should be the 3-step compile_b/compile_a/link path, not the pricier shortcut): 3 step(s) step 1: compile_b step 2: compile_a step 3: link DONE