Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
55 lines
1.6 KiB
Erlang
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}].
|