Advertisement
Guest User

Untitled

a guest
Jul 16th, 2019
55
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.24 KB | None | 0 0
  1. // Proof of concept
  2.  
  3. #include <vector>
  4. #include <functional>
  5. #include <cstdio>
  6. #include <utility>
  7.  
  8. //----------------------------------------------------------------------------
  9. //
  10. // Preliminaries: establish Mobius semantics (a reducer with side-effects)
  11. //
  12. // We're looking for something that takes a model (m) and an event (e)
  13. // and returns a new model (m) and list of effects (Eff f). Where m, e, and f
  14. // are all types provided by the user. `Eff f` means `std::vector<F>` for all
  15. // intents and purposes.
  16. //
  17. //----------------------------------------------------------------------------
  18.  
  19. // Drop everything in a namespacing struct to avoid template param boilerplate
  20. template <typename M, typename E, typename F>
  21. struct Mobius {
  22. // Effects are an array of F.
  23. using Effect = std::vector<F>;
  24.  
  25. // An effect sink is equivalent to std::vector<F>.push_back(F)
  26. using EffectSink = std::function<void(F)>;
  27.  
  28. // An EventSink is used by effect handlers to dispatch events back to the loop.
  29. // EventSink e = e -> Void
  30. using EventSink = std::function<void(E)>;
  31.  
  32. // The model can only be observed by subscribing to a Store using a callback
  33. // in this form.
  34. //
  35. // ModelCallback m = m -> Void
  36. using ModelCallback = std::function<void(M)>;
  37.  
  38. // An effect handler can dispatch one effect and needs an EventSink in case
  39. // the handler itself will generate more events.
  40. //
  41. // EffectHandler f e = (f, e -> Void) -> Void
  42. using EffectHandler = std::function<void(F,EventSink)>;
  43.  
  44. // Reducer m e f = (&m, e, F -> Void) -> Void
  45. using Reducer = std::function<void(M&, E, EffectSink)>;
  46.  
  47. class Store {
  48. private:
  49. M state;
  50. const Reducer reduce;
  51. std::vector<ModelCallback> subscribers; // TODO: these are not disposed
  52.  
  53. public:
  54.  
  55. Store(M initialState, Reducer reducer)
  56. : state(initialState)
  57. , reduce(reducer)
  58. {}
  59.  
  60. auto dispatch(E event) -> Effect {
  61. Effect effects;
  62. reduce(state, event, [&](F f){ effects.push_back(f); });
  63. for (const auto &observer : subscribers) {
  64. observer(state);
  65. }
  66. return effects;
  67. }
  68.  
  69. void subscribe(ModelCallback cb) {
  70. subscribers.push_back(cb);
  71. cb(state);
  72. }
  73. };
  74.  
  75. // This is just a Store with a fixed execution policy for effects
  76. class Loop {
  77. private:
  78. Store store;
  79. EffectHandler effectHandler;
  80.  
  81. // Recursively propagate event and effects
  82. void feedback(const Effect &effects) {
  83. for (const auto &effect : effects) {
  84. effectHandler(effect, [this](E event) {
  85. feedback(store.dispatch(event));
  86. });
  87. }
  88. }
  89.  
  90. public:
  91. Loop(M initialState,
  92. Reducer reducer,
  93. EffectHandler handler)
  94. : store(initialState, reducer)
  95. , effectHandler(handler)
  96. {}
  97.  
  98. void dispatch(E event) {
  99. feedback(store.dispatch(event));
  100. }
  101.  
  102. void subscribe(ModelCallback cb) {
  103. store.subscribe(cb);
  104. }
  105. };
  106. };
  107.  
  108. template <typename... T>
  109. struct RecursiveHelper {
  110. using type = std::function<RecursiveHelper(T...)>;
  111. RecursiveHelper(type f) : func(f) {}
  112. operator type () { return func; }
  113. type func;
  114. };
  115.  
  116. template <typename M, typename E, typename F>
  117. struct FPReducer {
  118. // An effect sink is equivalent to std::vector<F>.push_back(F)
  119. using EffectSink = std::function<void(F)>;
  120.  
  121. // StepFn m e f = (&m, e, (f) -> Void) -> StepFn m e f
  122. using StepFn = typename RecursiveHelper<M&, E, EffectSink>::type;
  123.  
  124. using State = std::pair<M, StepFn>;
  125.  
  126. using Domain = Mobius<State, E, F>;
  127.  
  128. static auto buildReducer() -> typename Domain::Reducer {
  129. return [](State &state, const E event, EffectSink emit) {
  130. auto &[model, step] = state;
  131. step = step(model, event, emit);
  132. };
  133. }
  134. };
  135.  
  136. template <typename E, typename F>
  137. struct ModelFreeReducer {
  138. // An effect sink is equivalent to std::vector<F>.push_back(F)
  139. using EffectSink = std::function<void(F)>;
  140.  
  141. using StepFn = typename RecursiveHelper<E, EffectSink>::type;
  142.  
  143. using Domain = Mobius<StepFn, E, F>;
  144.  
  145. static auto buildReducer() -> typename Domain::Reducer {
  146. return [](StepFn &step, const E event, EffectSink sink) {
  147. step = step(event, sink);
  148. };
  149. }
  150. };
  151.  
  152. //----------------------------------------------------------------------------
  153. //
  154. // Toy problem: counter
  155. //
  156. //----------------------------------------------------------------------------
  157.  
  158. // M = Int
  159. using Model = int;
  160.  
  161. // E = Increment | Decrement
  162. enum class Action {
  163. Increment,
  164. Decrement
  165. };
  166.  
  167. // F = PlaySound
  168. struct PlaySound {};
  169.  
  170. // This alias will clean up a few things
  171. using Toy = Mobius<Model, Action, PlaySound>;
  172.  
  173. // Define the reducer (this is the "update" function, the main logic of the feature)
  174. auto reducer = [](Model &model, const Action event, Toy::EffectSink emit) {
  175. switch (event) {
  176. case Action::Increment:
  177. model += 1; // update the model via mutable reference
  178. emit(PlaySound {}); // push two effects
  179. emit(PlaySound {});
  180. break;
  181. case Action::Decrement:
  182. model -= 1;
  183. break;
  184. }
  185. };
  186.  
  187. auto handler = [](PlaySound f, Toy::EventSink cb) {
  188. printf("<beep!>\n");
  189. cb(Action::Decrement);
  190. };
  191.  
  192. void toyDemo() {
  193. printf("-- counter demo\n");
  194. // create a store with initial state 0 and provide it a reducer
  195. auto loop = Toy::Loop(Model {0}, reducer, handler);
  196.  
  197. // observe the state updates
  198. loop.subscribe([](Model m) {
  199. printf("update: %d\n", m);
  200. });
  201.  
  202. // Dispatch an event to kick things off
  203. loop.dispatch(Action::Increment);
  204. }
  205.  
  206. //----------------------------------------------------------------------------
  207. //
  208. // Toy problem: high / low discrete state (model not used)
  209. //
  210. //----------------------------------------------------------------------------
  211.  
  212. struct Unit {};
  213. using FPR = FPReducer<Unit, Action, PlaySound>;
  214.  
  215. // Boilerplate: forward declare functions.
  216. auto low(Unit &model, const Action event, FPR::EffectSink emit) -> FPR::StepFn;
  217. auto high(Unit &model, const Action event, FPR::EffectSink emit) -> FPR::StepFn;
  218.  
  219. auto high(Unit &model, const Action event, FPR::EffectSink emit) -> FPR::StepFn {
  220. switch (event) {
  221. case Action::Increment:
  222. emit(PlaySound {});
  223. return high;
  224. case Action::Decrement:
  225. return low;
  226. }
  227. }
  228.  
  229. auto low(Unit &model, const Action event, FPR::EffectSink emit) -> FPR::StepFn {
  230. switch (event) {
  231. case Action::Increment:
  232. return high;
  233. case Action::Decrement:
  234. emit(PlaySound {});
  235. return low;
  236. }
  237. }
  238.  
  239. void lowHighDemo() {
  240. printf("-- low / high demo\n");
  241. auto loop = FPR::Domain::Loop(std::make_pair(Unit {}, low), FPR::buildReducer(), handler);
  242.  
  243. // observe the state updates
  244. loop.subscribe([](FPR::State state) {
  245. printf("update\n");
  246. });
  247.  
  248. loop.dispatch(Action::Increment);
  249. loop.dispatch(Action::Increment);
  250. }
  251.  
  252. //----------------------------------------------------------------------------
  253. //
  254. // Toy problem: high / low discrete state (model non-existent)
  255. //
  256. //----------------------------------------------------------------------------
  257.  
  258. using MFR = ModelFreeReducer<Action, PlaySound>;
  259.  
  260. // Boilerplate: forward declare functions.
  261. auto low2(const Action event, MFR::EffectSink emit) -> MFR::StepFn;
  262. auto high2(const Action event, MFR::EffectSink emit) -> MFR::StepFn;
  263.  
  264. auto high2(const Action event, MFR::EffectSink emit) -> MFR::StepFn {
  265. switch (event) {
  266. case Action::Increment:
  267. emit(PlaySound {});
  268. return high2;
  269. case Action::Decrement:
  270. return low2;
  271. }
  272. }
  273.  
  274. auto low2(const Action event, MFR::EffectSink emit) -> MFR::StepFn {
  275. switch (event) {
  276. case Action::Increment:
  277. return high2;
  278. case Action::Decrement:
  279. emit(PlaySound {});
  280. return low2;
  281. }
  282. }
  283.  
  284. void modelFreeDemo() {
  285. printf("-- model-free low / high demo\n");
  286. auto loop = MFR::Domain::Loop(low2, MFR::buildReducer(), handler);
  287.  
  288. // observe the state updates
  289. loop.subscribe([](MFR::StepFn state) {
  290. printf("update\n");
  291. });
  292.  
  293. loop.dispatch(Action::Increment);
  294. loop.dispatch(Action::Increment);
  295. }
  296. int main() {
  297. toyDemo();
  298. lowHighDemo();
  299. modelFreeDemo();
  300. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement