{"action":"created","issue":{"url":"https://api.github.com/repos/paldepind/functional-frontend-architecture/issues/20","labels_url":"https://api.github.com/repos/paldepind/functional-frontend-architecture/issues/20/labels{/name}","comments_url":"https://api.github.com/repos/paldepind/functional-frontend-architecture/issues/20/comments","events_url":"https://api.github.com/repos/paldepind/functional-frontend-architecture/issues/20/events","html_url":"https://github.com/paldepind/functional-frontend-architecture/issues/20","id":117922035,"number":20,"title":"Alternatives Async/side effect models","user":{"login":"yelouafi","id":5453835,"avatar_url":"https://avatars.githubusercontent.com/u/5453835?v=3","gravatar_id":"","url":"https://api.github.com/users/yelouafi","html_url":"https://github.com/yelouafi","followers_url":"https://api.github.com/users/yelouafi/followers","following_url":"https://api.github.com/users/yelouafi/following{/other_user}","gists_url":"https://api.github.com/users/yelouafi/gists{/gist_id}","starred_url":"https://api.github.com/users/yelouafi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yelouafi/subscriptions","organizations_url":"https://api.github.com/users/yelouafi/orgs","repos_url":"https://api.github.com/users/yelouafi/repos","events_url":"https://api.github.com/users/yelouafi/events{/privacy}","received_events_url":"https://api.github.com/users/yelouafi/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":31,"created_at":"2015-11-19T22:22:23Z","updated_at":"2015-12-11T10:09:07Z","closed_at":null,"body":"it's been some time since I'm thinking on Async/Side effects models in Elm architecture. I'd really like to write some article on this to complete the first one. I know it was already discussed in #13 but i'd like to present some other alternatives here for discussion. i´ll motivate the need for alternative models and will illustrate with the standard Counter example. Sorry for the long post.\r\n\r\nCurrently the standard Elm solution works by turning the signature of `update` from this\r\n\r\n```javascript\r\nupdate : (state, action) -> state\r\n```\r\n\r\nto this\r\n```javascript\r\nupdate : (state, action) -> (state, [Future Action])\r\n\r\n// using Reactive streams\r\nupdate : (state, action) -> (state, Stream Action)\r\n\r\n// using callback with Node CSP style\r\nupdate : (state, action, dispatch -> () ) -> state\r\n```\r\n\r\nWhatever solution we choose, it allows us to express side effects and fire asynchronous actions. However, there is a thing i dislike here : in the first effect-less version we had a nice pure function ` (state, action) -> state`. It's predictable and can be tested with ease. In contrast, it's not that easy in the effect-full version : we now have to mock the function environment to test the effect-full reactions.\r\n\r\nIMO it'd be preferable to keep the `update` pure and clean of any side effect. Its only purpose should be : calculate a new state giving an existing one and a given action. \r\n\r\nSo i present here 2 alternatives side effect models for discussion : \r\n\r\n1- The first model is taken directly from [redux](https://github.com/rackt/redux) (which was inspired by Elm). Like in Elm architecture, redux has pure functions called reducers with the same signature as `update`. A pretty standard way to fire asynchronous actions in redux is through *thunks* : i.e. callback function of the form `dispatch -> ()`. \r\n\r\nI'll illustrate with the Counter example and an asynchronous increment action\r\n\r\n```javascript\r\n// Synchronous : () -> Action\r\nfunction increment() {\r\n  return { type: 'increment' }\r\n}\r\n\r\n// Asynchronous : () -> ( dispatch -> () )\r\nfunction incrementAsync() {\r\n  return dispatch => {\r\n    setTimeout( () => dispatch( increment() ),  1000)\r\n  }\r\n}\r\n\r\nconst view = ({state, dispatch}) =>\r\n    <div>\r\n      <button on-click={[dispatch, increment()]}>+</button>\r\n      <div>{state}</div>\r\n      <button on-click={[dispatch, incrementAsync()]}>+ (Async)</button>\r\n    </div>;\r\n\r\nconst init = () => 0;\r\n\r\nconst update = (state, action) => type === 'increment' ? state + 1 : state;\r\n```\r\n\r\nThe idea is : instead of firing a normal action, the component fires an *action dispatcher* (the thunk). Now our main dispatcher has to be enhanced to know about thunks\r\n\r\n```javascript```\r\nfunction dispatch(action) {\r\n  if(typeof action === 'function')\r\n    action(dispatch);\r\n  else {\r\n    state = Counter.update(action);\r\n    updateUI();\r\n  }\r\n}\r\n```\r\n\r\nThe main benefit of the above approach is to keep the `update` function clean of any effect (this is also a golden rule in redux : a reducer must not do any side effect like api calls...). Another benefit is that you can fire multiples actions from the thunk which is useful to manage a flow of multiple actions.\r\n\r\n2- the second alternative was inspired by [this interesting discussion](https://github.com/rackt/redux/issues/891) (also in the redux site, yeah!), the idea is to separate Effect creation and execution in the same way we separate Action creation and update, the `update` signature will look quite the same as in the current Elm solution\r\n\r\n```javascript\r\nupdate : (state, action) -> (state, Effect)\r\n```\r\n\r\nHowever, the `Effect` above is no longer a Future or a Promise or whatever, it's a just a data object much like the actions that describes the intent. To run the actual effect we'll provide the component with another method `execute` which will run the real side effect. So back to the Counter example\r\n\r\n```javascript\r\n\r\n// an union type to describe \"effect-full\" value\r\nconst UpdateResult = Type({\r\n  Pure: [T],\r\n  WithEffects: [T, T]\r\n});\r\n\r\n\r\nconst Action = Type({\r\n  Increment      : [],\r\n  IncrementLater : []\r\n})\r\n\r\nconst Effect = Type({\r\n  IncrementAsync : []\r\n});\r\n\r\nconst view = ({state, dispatch}) =>\r\n    <div>\r\n      <button on-click={[dispatch, Action.Increment()]}>+</button>\r\n      <div>{state}</div>\r\n      <button on-click={[dispatch, Action.IncrementLater()]}>+ (Async)</button>\r\n    </div>;\r\n\r\n// pure and withEffects are 2 helper factories\r\nconst init = () => pure(0);\r\n\r\nconst update = (state, action) => Action.case({\r\n  Increment       : () => pure(state + 1),\r\n  IncrementLater  : () => withEffects(state, Effect.IncrementAsync())\r\n}, action);\r\n\r\nconst execute = (state, effect, dispatch) => Effect.case({\r\n  IncrementAsync: () => {\r\n    setTimeout(() => dispatch(Action.Increment()), 1000)\r\n  }\r\n}, effect);\r\n\r\nexport default { view, init, update, Action, execute, Effect };\r\n```\r\n\r\nSo the component have now 2 additional properties : `Effect` which like `Action` documents side effects carried by the component and `execute`, which like `update`, run the actual effect. This may seem more boilerplate but has the advantage of keeping the `update` function pure : we can now test its return value by just checking the returned state and eventually the side effect data. Another advantage may be that Effects are now documented explicitly in the component. \r\n\r\nThe main dispatcher will look something like\r\n\r\n```javascript\r\nfunction updateStatePure(newState) {\r\n  state = newState;\r\n  updateUI();\r\n}\r\n\r\nexport function dispatch(action) {\r\n  const updateResult = App.update(state, action);\r\n  UpdateResult.case({\r\n    Pure: v => updateStatePure(v),\r\n    WithEffects: (v, effect) => {\r\n      updateStatePure(v);\r\n      Counter.execute(effect, dispatch);\r\n    }\r\n  })\r\n}\r\n```\r\n \r\n[Here is an example](https://github.com/yelouafi/elm-arch-with-snabbdom/tree/master/app/public/counterList/js) of the above approach with nested components.\r\n\r\nWhat do you think of those 2 models ? the redux solution seems more simple to me as it doesn't add more boilerplate to the current model. The 2nd solution has the advantage to be more explicit and clearly documents the Effects carried by a component.\r\n\r\nI'd be also nice to hear the opinion of @evancz or also @gaearon the creator of redux, knowing his Elm background"},"comment":{"url":"https://api.github.com/repos/paldepind/functional-frontend-architecture/issues/comments/163898238","html_url":"https://github.com/paldepind/functional-frontend-architecture/issues/20#issuecomment-163898238","issue_url":"https://api.github.com/repos/paldepind/functional-frontend-architecture/issues/20","id":163898238,"user":{"login":"slorber","id":749374,"avatar_url":"https://avatars.githubusercontent.com/u/749374?v=3","gravatar_id":"","url":"https://api.github.com/users/slorber","html_url":"https://github.com/slorber","followers_url":"https://api.github.com/users/slorber/followers","following_url":"https://api.github.com/users/slorber/following{/other_user}","gists_url":"https://api.github.com/users/slorber/gists{/gist_id}","starred_url":"https://api.github.com/users/slorber/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/slorber/subscriptions","organizations_url":"https://api.github.com/users/slorber/orgs","repos_url":"https://api.github.com/users/slorber/repos","events_url":"https://api.github.com/users/slorber/events{/privacy}","received_events_url":"https://api.github.com/users/slorber/received_events","type":"User","site_admin":false},"created_at":"2015-12-11T10:09:07Z","updated_at":"2015-12-11T10:09:07Z","body":"@yelouafi unfortunatly I've never used generators yet so I'll have to learn a bit more about them :)"}}