Files
rose-ash/next/kernel/projection.erl
giles 4956a6d8ae
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
fed-sx-m1: Step 7a — pure-functional projection driver + 12 tests
2026-05-28 05:48:30 +00:00

55 lines
1.6 KiB
Erlang

-module(projection).
-export([new/2, new/3, fold_activity/2, replay/2,
name/1, state/1, fold_fn/1]).
%% Pure-functional projection driver per design §10.
%%
%% A projection is a property list:
%% [{name, atom}, {state, term}, {fold, fun}]
%%
%% The fold function is `fun (Activity, State) -> NewState`. v1
%% uses Erlang funs as the fold body — the genesis bundle's SX
%% `:fold` bodies are stored as binaries; an SX-source eval
%% bridge will plug them into the same projection record once
%% it lands (Step 7d). For now, callers supply Erlang funs
%% directly when constructing a projection.
%%
%% `replay/2` is the cold-start primitive: fold an activity
%% list (e.g. `log:entries/1`) through the projection from its
%% initial state.
new(Name, InitialState) ->
new(Name, InitialState, fun (_Activity, S) -> S end).
new(Name, InitialState, FoldFn) ->
[{name, Name}, {state, InitialState}, {fold, FoldFn}].
fold_activity(Proj, Activity) ->
Fn = fold_fn(Proj),
S0 = state(Proj),
S1 = Fn(Activity, S0),
set_field(state, S1, Proj).
replay(Proj, Activities) ->
fold_each(Proj, Activities).
fold_each(Proj, []) -> Proj;
fold_each(Proj, [A | Rest]) ->
fold_each(fold_activity(Proj, A), Rest).
%% Accessors
name(Proj) -> field(name, Proj).
state(Proj) -> field(state, Proj).
fold_fn(Proj) -> field(fold, Proj).
%% Internal
field(K, [{K, V} | _]) -> V;
field(K, [_ | Rest]) -> field(K, Rest);
field(_, []) -> erlang:error(badkey).
set_field(K, V, [{K, _} | Rest]) -> [{K, V} | Rest];
set_field(K, V, [P | Rest]) -> [P | set_field(K, V, Rest)];
set_field(K, V, []) -> [{K, V}].