Skip to content

Instantly share code, notes, and snippets.

@saem
Created August 1, 2016 03:35
Show Gist options
  • Select an option

  • Save saem/dc2a5bceaac97bed1892fc8fef0054ab to your computer and use it in GitHub Desktop.

Select an option

Save saem/dc2a5bceaac97bed1892fc8fef0054ab to your computer and use it in GitHub Desktop.
Read about State, Action, Model (http://sam.js.org/), which is fashioned after TLA+'s concept of a step, I'm borrowing Paxos terminology: Propose, Accept, Learn as a step breakdown. Just doing some thinking out loud.
//@flow
/**
* The model, broken down into three parts (data, ui, & effects).
*
* data - canonical representation/interface with various backends.
* ui - the UI state
* effects - effects we want to trigger
*
* Model should probably be a parameterized type
*/
type Model = {data: any, ui: any, effects: any}
var model: Model = {
data: {}, // canonical data
ui: {}, // intermediate UI state
effects: {} // any effects that need to be sent off
};
// Propose, accept, learn as a 'step' breakdown
// the acceptor, is an accept function with the model & reducer parameters bound
type ModelConsumer = (Model) => void;
const propose = (acceptor: ModelConsumer) => (newModel: Model) => {
acceptor(newModel);
};
type Reducer = (Model, Model) => Model;
const accept = (model: Model) =>
(reducer: Reducer) =>
(proposal: Model) => {
// reducer does the real mutation logic to the model
model = reducer(model, proposal);
};
const learn = (learner: ModelConsumer) => (model: Model) => {
learner(model);
};
// Wiring together propose, accept, and learn
const mutator = (reducer: Reducer) =>
(learner: ModelConsumer) =>
(oldModel: Model, proposal: Model) => {
const model = reducer(oldModel, proposal);
// make changes to the model, with some guidelines:
// 1 - make any changes to data, if from the server
// 2 - make any changes to ui, if ui state
// 3 - enqueue any effects that we want fired off
learner(model);
};
const modelReducer: Reducer = (oldModel: Model, newModel: Model) => newModel;
const learner: ModelConsumer = (learners: Array<) => (model: Model) =>
const proposer = propose(accept(model)(mutator(modelReducer)(learner)));
const action = (proposer: ModelConsumer) => (fn: (Any) => Model) => (rawData) => {
proposer(fn(rawData));
};
// @todo These learners need to be properly initialized with their dependencies
// via partial function application, as above
const render = (model) => {
// rendering code here
};
const effects = (model) => {
// fire off effects such as HTTP requests here
}
const nextAction = (actions) => (model) => {
// Invoke follow-on actions because of state changes
};
// The nextAction should be the last of the learners
const learners = [render, effects, nextAction];
@saem
Copy link
Author

saem commented Aug 1, 2016

There are some significant next steps that need to be thought through.

Model:

Mostly as a 'this seems nice' type thing I separated the UI, Data, and Effects parts of the model. It makes a lot of sense in some ways, but I'm not entirely convinced just yet. The model should also be parameterized by the three subsections. Also some attention needs to be paid to this whole thing, I see this pattern being recursively applied like the Elm Architecture, which means the whole mutation bit needs to be moved up one more layer, perhaps.

Reducers:

The various reducers can probably be cleaned up in that they shouldn't see the entire model, and currently, any attempt at proposing a new model state means knowing the whole model, rather than only the parts we're concerned about. I think the reducer pattern held in one area might make sense. But at the same time, it seems like a goofy dispatch/messaging layer that has its own set of issues.

Insights:

The only real insight thus far is that nextAction is just another learner, one that happens to be 'distinguished' in that it can fire further actions, and should (?) be the last of the learners.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment