Educational Topic: Advanced recursion patterns and two-phase computation
Real-World Application: Fibonacci, binomial coefficients, and tree recursion optimization
Case Study: UnifyWeaver’s fold pattern integration and automatic code generation
This appendix explores fold patterns - a sophisticated approach to recursive computation that separates structure building from value computation. This pattern was recently integrated into UnifyWeaver’s main compilation pipeline, providing an elegant alternative to memoization for certain types of tree recursion.
The fold pattern demonstrates advanced compiler design principles, template-based code generation, and the power of separating concerns in algorithm design.
A fold pattern splits recursive computation into two distinct phases:
This separation provides several benefits:
Traditional Recursive Approach (Fibonacci):
fib(0, 0).
fib(1, 1).
fib(N, F) :-
N > 1,
N1 is N - 1, N2 is N - 2,
fib(N1, F1), fib(N2, F2), % Direct recursive calls
F is F1 + F2. % Computation mixed with recursion
Fold Pattern Approach:
% Phase 1: Structure Builder
fib_graph(0, leaf(0)).
fib_graph(1, leaf(1)).
fib_graph(N, node(N, [L, R])) :-
N > 1,
N1 is N - 1, N2 is N - 2,
fib_graph(N1, L), % Build dependency tree
fib_graph(N2, R).
% Phase 2: Fold Computer
fold_fib(leaf(V), V).
fold_fib(node(_, [L, R]), V) :-
fold_fib(L, VL), % Fold left subtree
fold_fib(R, VR), % Fold right subtree
V is VL + VR. % Combine results
% Phase 3: Wrapper
fib_fold(N, F) :-
fib_graph(N, Graph), % Build structure
fold_fib(Graph, F). % Compute value
Fold patterns use a standardized tree representation:
Leaf Nodes: leaf(Value)
Internal Nodes: node(Label, Children)
Label - Identifies the computation (often the input parameters)Children - List of child nodes [Child1, Child2, ...]Examples:
% Fibonacci graph for fib(3)
node(3, [
node(2, [
leaf(1),
leaf(0)
]),
leaf(1)
])
% This represents: fib(3) = fib(2) + fib(1) = (fib(1) + fib(0)) + fib(1)
The Challenge: Fibonacci has exponential recursive calls, needs optimization.
Structure Builder:
fib_graph(0, leaf(0)).
fib_graph(1, leaf(1)).
fib_graph(N, node(N, [L, R])) :-
N > 1,
N1 is N - 1, N2 is N - 2,
fib_graph(N1, L),
fib_graph(N2, R).
Fold Computer:
fold_fib(leaf(V), V).
fold_fib(node(_, [L, R]), V) :-
fold_fib(L, VL), fold_fib(R, VR),
V is VL + VR.
Usage:
?- fib_fold(5, F).
F = 5.
?- fib_graph(4, Graph).
Graph = node(4, [
node(3, [
node(2, [leaf(1), leaf(0)]),
leaf(1)
]),
node(2, [leaf(1), leaf(0)])
]).
The Challenge: Computing C(n,k) involves Pascal’s triangle relationships.
Structure Builder:
binom_graph(_, 0, leaf(1)).
binom_graph(N, N, leaf(1)) :- N >= 0.
binom_graph(N, K, node((N,K), [L, R])) :-
N > 0, K > 0, K < N,
N1 is N - 1, K1 is K - 1,
binom_graph(N1, K1, L), % C(n-1,k-1)
binom_graph(N1, K, R). % C(n-1,k)
Fold Computer:
fold_binom(leaf(V), V).
fold_binom(node(_, [L, R]), V) :-
fold_binom(L, VL), fold_binom(R, VR),
V is VL + VR. % Pascal's triangle: C(n,k) = C(n-1,k-1) + C(n-1,k)
Usage:
?- binom_fold(5, 2, C).
C = 10. % C(5,2) = 10 combinations
The Challenge: Parse and evaluate mathematical expressions with precedence.
Structure Builder:
expr_graph(N, leaf(N)) :- number(N).
expr_graph(X + Y, node(plus, [L, R])) :-
expr_graph(X, L),
expr_graph(Y, R).
expr_graph(X * Y, node(mult, [L, R])) :-
expr_graph(X, L),
expr_graph(Y, R).
Fold Computer:
fold_expr(leaf(V), V).
fold_expr(node(plus, [L, R]), V) :-
fold_expr(L, VL), fold_expr(R, VR),
V is VL + VR.
fold_expr(node(mult, [L, R]), V) :-
fold_expr(L, VL), fold_expr(R, VR),
V is VL * VR.
Usage:
?- expr_fold(2 + 3 * 4, V).
V = 14. % Respects precedence: 2 + (3 * 4)
Writing fold patterns manually for each predicate is time-consuming and error-prone. UnifyWeaver includes an automatic generation system that detects fold patterns and creates the helper predicates automatically.
Automatic Detection Criteria:
is_tree_fold_pattern(Pred/Arity) :-
% 1. Has tree recursion (multiple recursive calls)
has_tree_recursion(Pred/Arity),
% 2. Uses forbid_linear_recursion directive
forbid_linear_recursion(Pred/Arity),
% 3. Follows arithmetic combination pattern
has_arithmetic_combination(Pred/Arity).
Example Detection:
% This predicate will be detected as fold pattern
:- assertz(forbid_linear_recursion(test_fib/2)).
test_fib(0, 0).
test_fib(1, 1).
test_fib(N, F) :-
N > 1,
N1 is N - 1, N2 is N - 2,
test_fib(N1, F1), test_fib(N2, F2), % Tree recursion
F is F1 + F2. % Arithmetic combination
The fold_helper_generator.pl module automatically creates three helper predicates:
1. Graph Builder: pred_graph/N
% Generated automatically
test_fib_graph(0, leaf(0)).
test_fib_graph(1, leaf(1)).
test_fib_graph(N, node(N, [L, R])) :-
N > 1,
N1 is N - 1, N2 is N - 2,
test_fib_graph(N1, L),
test_fib_graph(N2, R).
2. Fold Computer: fold_pred/2
% Generated automatically
fold_test_fib(leaf(V), V).
fold_test_fib(node(_, [L, R]), V) :-
fold_test_fib(L, VL),
fold_test_fib(R, VR),
V is VL + VR.
3. Wrapper Predicate: pred_fold/N
% Generated automatically
test_fib_fold(N, F) :-
test_fib_graph(N, Graph),
fold_test_fib(Graph, F).
Variable Mapping Algorithm:
generate_fold_helpers(Pred/Arity, Helpers) :-
functor(Head, Pred, Arity),
Head =.. [Pred|Args],
% Split arguments: inputs and output
append(Inputs, [Output], Args),
% Generate graph builder: pred_graph(Inputs..., Graph)
GraphPred =.. [GraphName|Inputs, Graph],
% Generate fold computer: fold_pred(Graph, Output)
FoldPred =.. [FoldName, Graph, Output],
% Generate wrapper: pred_fold(Inputs..., Output)
WrapperPred =.. [WrapperName|Args].
Copy-Term Variable Preservation:
The generator uses copy_term/2 to ensure variable bindings are preserved correctly during transformation:
transform_body(OriginalBody, GraphBody) :-
copy_term(OriginalBody, GraphBody), % Preserve variable sharing
replace_recursive_calls(GraphBody). % Transform calls to graph builders
Fold patterns are integrated into UnifyWeaver’s advanced recursion compiler with specific priority:
tail → linear → FOLD → tree → mutual
Why this order?
compile_advanced_recursive(Pred/Arity, Options, BashCode) :-
( % Try fold pattern if explicitly requested
forbid_linear_recursion(Pred/Arity),
is_tree_fold_pattern(Pred/Arity)
-> compile_fold_pattern(Pred/Arity, Options, BashCode)
; % Otherwise try other patterns
try_other_patterns(Pred/Arity, Options, BashCode)
).
The fold pattern compiler generates complete bash implementations:
Generated Structure (1903 characters of code):
#!/bin/bash
# test_fib - compiled fold pattern
# Graph builder function
test_fib_graph() {
local n="$1"
if [[ "$n" -eq 0 ]]; then
echo "leaf(0)"
return
fi
if [[ "$n" -eq 1 ]]; then
echo "leaf(1)"
return
fi
local n1=$((n - 1))
local n2=$((n - 2))
local left=$(test_fib_graph "$n1")
local right=$(test_fib_graph "$n2")
echo "node($n,[$left,$right])"
}
# Fold computer function
fold_test_fib() {
local graph="$1"
if [[ "$graph" =~ leaf\(([0-9]+)\) ]]; then
echo "${BASH_REMATCH[1]}"
return
fi
# Parse node structure and recursively fold
# ... complex parsing and computation logic
}
# Wrapper function
test_fib_fold() {
local n="$1"
local graph=$(test_fib_graph "$n")
fold_test_fib "$graph"
}
# Main entry point
test_fib() {
test_fib_fold "$@"
}
Best Use Cases:
Performance Comparison:
| Pattern | Time Complexity | Space Complexity | Code Clarity |
|---|---|---|---|
| Naive recursion | O(2^n) - exponential | O(n) - call stack | ⭐⭐⭐ Very clear |
| Memoization | O(n) - linear | O(n) - memo table | ⭐⭐ Moderate |
| Fold pattern | O(n) - linear | O(n) - structure | ⭐⭐⭐⭐ Excellent |
| Iterative | O(n) - linear | O(1) - constant | ⭐ Less clear |
Advantages:
Disadvantages:
Task: Implement a fold pattern for computing factorials.
Starter Code:
% Define your graph builder
fact_graph(0, ?).
fact_graph(N, ?) :- N > 0, ?
% Define your fold computer
fold_fact(?, ?).
fold_fact(?, ?) :- ?
% Create wrapper
fact_fold(N, F) :- ?
Solution:
fact_graph(0, leaf(1)).
fact_graph(N, node(N, [Child])) :-
N > 0, N1 is N - 1,
fact_graph(N1, Child).
fold_fact(leaf(V), V).
fold_fact(node(N, [Child]), V) :-
fold_fact(Child, CV),
V is N * CV.
fact_fold(N, F) :- fact_graph(N, Graph), fold_fact(Graph, F).
Task: Use UnifyWeaver’s automatic generation for your factorial fold pattern.
Steps:
forbid_linear_recursion directiveTask: Create a visualization of the fold pattern structure.
Code:
visualize_graph(leaf(V)) :-
format('~w', [V]).
visualize_graph(node(Label, Children)) :-
format('~w(', [Label]),
maplist(visualize_graph, Children),
format(')', []).
Test:
?- fib_graph(4, G), visualize_graph(G).
4(3(2(1,0),1),2(1,0))
Task: Create multiple fold operations for the same structure.
Example - Statistics on Fibonacci Structure:
% Count nodes in structure
fold_count_nodes(leaf(_), 1).
fold_count_nodes(node(_, Children), Count) :-
maplist(fold_count_nodes, Children, Counts),
sumlist(Counts, ChildCount),
Count is ChildCount + 1.
% Find maximum value in structure
fold_max_value(leaf(V), V).
fold_max_value(node(_, Children), Max) :-
maplist(fold_max_value, Children, Values),
max_list(Values, Max).
Bottom-Up Folding (Standard):
fold_pred(leaf(V), V).
fold_pred(node(_, Children), Result) :-
maplist(fold_pred, Children, ChildResults),
combine(ChildResults, Result).
Top-Down Folding (With Accumulator):
fold_pred_acc(Graph, Acc, Result) :-
fold_pred_acc(Graph, Acc, 0, Result).
fold_pred_acc(leaf(V), Acc, _, Result) :-
Result is Acc + V.
fold_pred_acc(node(_, Children), Acc, Level, Result) :-
NewAcc is Acc + Level,
Level1 is Level + 1,
maplist(fold_pred_acc_helper(NewAcc, Level1), Children, Results),
sumlist(Results, Result).
For large structures, fold operations can be parallelized:
# Bash parallel folding
fold_parallel() {
local graph="$1"
# Identify independent subtrees
extract_subtrees "$graph" > subtrees.txt
# Process subtrees in parallel
parallel -j 4 fold_subtree :::: subtrees.txt > results.txt
# Combine results
combine_results < results.txt
}
For dynamic structures, incremental folding can reuse previous computations:
incremental_fold(Graph, Cache, Result, NewCache) :-
( member(Graph-CachedResult, Cache)
-> Result = CachedResult, NewCache = Cache
; compute_fold(Graph, Result),
NewCache = [Graph-Result|Cache]
).
:- constraint(unique(false)). % Allow duplicate intermediate results
:- constraint(unordered(true)). % Order doesn't matter for folding
:- forbid_linear_recursion(fib/2). % Force fold pattern
fib(N, F) :- fib_fold(N, F).
:- firewall([bash_generation(allowed), memoization(forbidden)]).
% Only bash generation allowed, memoization blocked
% Fold pattern provides alternative to memoization
generate_fold_bash(Pred/Arity, BashCode) :-
template_system:render_named_template(
fold_pattern_template,
[pred=Pred, arity=Arity],
BashCode
).
Two-Phase Computation:
Automatic Generation:
Integration Architecture:
Ideal scenarios:
Avoid when:
The fold pattern demonstrates that elegant software design often comes from separating what you’re computing from how you’re computing it.
Related UnifyWeaver Documentation:
Academic References:
Practical Applications:
This appendix documents the fold pattern implementation completed October 14, 2025, demonstrating advanced recursion compilation techniques and automatic code generation in production systems.