Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Proof of concept
- #include <vector>
- #include <functional>
- #include <cstdio>
- #include <utility>
- //----------------------------------------------------------------------------
- //
- // Preliminaries: establish Mobius semantics (a reducer with side-effects)
- //
- // We're looking for something that takes a model (m) and an event (e)
- // and returns a new model (m) and list of effects (Eff f). Where m, e, and f
- // are all types provided by the user. `Eff f` means `std::vector<F>` for all
- // intents and purposes.
- //
- //----------------------------------------------------------------------------
- // Drop everything in a namespacing struct to avoid template param boilerplate
- template <typename M, typename E, typename F>
- struct Mobius {
- // Effects are an array of F.
- using Effect = std::vector<F>;
- // An effect sink is equivalent to std::vector<F>.push_back(F)
- using EffectSink = std::function<void(F)>;
- // An EventSink is used by effect handlers to dispatch events back to the loop.
- // EventSink e = e -> Void
- using EventSink = std::function<void(E)>;
- // The model can only be observed by subscribing to a Store using a callback
- // in this form.
- //
- // ModelCallback m = m -> Void
- using ModelCallback = std::function<void(M)>;
- // An effect handler can dispatch one effect and needs an EventSink in case
- // the handler itself will generate more events.
- //
- // EffectHandler f e = (f, e -> Void) -> Void
- using EffectHandler = std::function<void(F,EventSink)>;
- // Reducer m e f = (&m, e, F -> Void) -> Void
- using Reducer = std::function<void(M&, E, EffectSink)>;
- class Store {
- private:
- M state;
- const Reducer reduce;
- std::vector<ModelCallback> subscribers; // TODO: these are not disposed
- public:
- Store(M initialState, Reducer reducer)
- : state(initialState)
- , reduce(reducer)
- {}
- auto dispatch(E event) -> Effect {
- Effect effects;
- reduce(state, event, [&](F f){ effects.push_back(f); });
- for (const auto &observer : subscribers) {
- observer(state);
- }
- return effects;
- }
- void subscribe(ModelCallback cb) {
- subscribers.push_back(cb);
- cb(state);
- }
- };
- // This is just a Store with a fixed execution policy for effects
- class Loop {
- private:
- Store store;
- EffectHandler effectHandler;
- // Recursively propagate event and effects
- void feedback(const Effect &effects) {
- for (const auto &effect : effects) {
- effectHandler(effect, [this](E event) {
- feedback(store.dispatch(event));
- });
- }
- }
- public:
- Loop(M initialState,
- Reducer reducer,
- EffectHandler handler)
- : store(initialState, reducer)
- , effectHandler(handler)
- {}
- void dispatch(E event) {
- feedback(store.dispatch(event));
- }
- void subscribe(ModelCallback cb) {
- store.subscribe(cb);
- }
- };
- };
- template <typename... T>
- struct RecursiveHelper {
- using type = std::function<RecursiveHelper(T...)>;
- RecursiveHelper(type f) : func(f) {}
- operator type () { return func; }
- type func;
- };
- template <typename M, typename E, typename F>
- struct FPReducer {
- // An effect sink is equivalent to std::vector<F>.push_back(F)
- using EffectSink = std::function<void(F)>;
- // StepFn m e f = (&m, e, (f) -> Void) -> StepFn m e f
- using StepFn = typename RecursiveHelper<M&, E, EffectSink>::type;
- using State = std::pair<M, StepFn>;
- using Domain = Mobius<State, E, F>;
- static auto buildReducer() -> typename Domain::Reducer {
- return [](State &state, const E event, EffectSink emit) {
- auto &[model, step] = state;
- step = step(model, event, emit);
- };
- }
- };
- template <typename E, typename F>
- struct ModelFreeReducer {
- // An effect sink is equivalent to std::vector<F>.push_back(F)
- using EffectSink = std::function<void(F)>;
- using StepFn = typename RecursiveHelper<E, EffectSink>::type;
- using Domain = Mobius<StepFn, E, F>;
- static auto buildReducer() -> typename Domain::Reducer {
- return [](StepFn &step, const E event, EffectSink sink) {
- step = step(event, sink);
- };
- }
- };
- //----------------------------------------------------------------------------
- //
- // Toy problem: counter
- //
- //----------------------------------------------------------------------------
- // M = Int
- using Model = int;
- // E = Increment | Decrement
- enum class Action {
- Increment,
- Decrement
- };
- // F = PlaySound
- struct PlaySound {};
- // This alias will clean up a few things
- using Toy = Mobius<Model, Action, PlaySound>;
- // Define the reducer (this is the "update" function, the main logic of the feature)
- auto reducer = [](Model &model, const Action event, Toy::EffectSink emit) {
- switch (event) {
- case Action::Increment:
- model += 1; // update the model via mutable reference
- emit(PlaySound {}); // push two effects
- emit(PlaySound {});
- break;
- case Action::Decrement:
- model -= 1;
- break;
- }
- };
- auto handler = [](PlaySound f, Toy::EventSink cb) {
- printf("<beep!>\n");
- cb(Action::Decrement);
- };
- void toyDemo() {
- printf("-- counter demo\n");
- // create a store with initial state 0 and provide it a reducer
- auto loop = Toy::Loop(Model {0}, reducer, handler);
- // observe the state updates
- loop.subscribe([](Model m) {
- printf("update: %d\n", m);
- });
- // Dispatch an event to kick things off
- loop.dispatch(Action::Increment);
- }
- //----------------------------------------------------------------------------
- //
- // Toy problem: high / low discrete state (model not used)
- //
- //----------------------------------------------------------------------------
- struct Unit {};
- using FPR = FPReducer<Unit, Action, PlaySound>;
- // Boilerplate: forward declare functions.
- auto low(Unit &model, const Action event, FPR::EffectSink emit) -> FPR::StepFn;
- auto high(Unit &model, const Action event, FPR::EffectSink emit) -> FPR::StepFn;
- auto high(Unit &model, const Action event, FPR::EffectSink emit) -> FPR::StepFn {
- switch (event) {
- case Action::Increment:
- emit(PlaySound {});
- return high;
- case Action::Decrement:
- return low;
- }
- }
- auto low(Unit &model, const Action event, FPR::EffectSink emit) -> FPR::StepFn {
- switch (event) {
- case Action::Increment:
- return high;
- case Action::Decrement:
- emit(PlaySound {});
- return low;
- }
- }
- void lowHighDemo() {
- printf("-- low / high demo\n");
- auto loop = FPR::Domain::Loop(std::make_pair(Unit {}, low), FPR::buildReducer(), handler);
- // observe the state updates
- loop.subscribe([](FPR::State state) {
- printf("update\n");
- });
- loop.dispatch(Action::Increment);
- loop.dispatch(Action::Increment);
- }
- //----------------------------------------------------------------------------
- //
- // Toy problem: high / low discrete state (model non-existent)
- //
- //----------------------------------------------------------------------------
- using MFR = ModelFreeReducer<Action, PlaySound>;
- // Boilerplate: forward declare functions.
- auto low2(const Action event, MFR::EffectSink emit) -> MFR::StepFn;
- auto high2(const Action event, MFR::EffectSink emit) -> MFR::StepFn;
- auto high2(const Action event, MFR::EffectSink emit) -> MFR::StepFn {
- switch (event) {
- case Action::Increment:
- emit(PlaySound {});
- return high2;
- case Action::Decrement:
- return low2;
- }
- }
- auto low2(const Action event, MFR::EffectSink emit) -> MFR::StepFn {
- switch (event) {
- case Action::Increment:
- return high2;
- case Action::Decrement:
- emit(PlaySound {});
- return low2;
- }
- }
- void modelFreeDemo() {
- printf("-- model-free low / high demo\n");
- auto loop = MFR::Domain::Loop(low2, MFR::buildReducer(), handler);
- // observe the state updates
- loop.subscribe([](MFR::StepFn state) {
- printf("update\n");
- });
- loop.dispatch(Action::Increment);
- loop.dispatch(Action::Increment);
- }
- int main() {
- toyDemo();
- lowHighDemo();
- modelFreeDemo();
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement