{"action":"created","issue":{"url":"https://api.github.com/repos/gaearon/redux/issues/176","labels_url":"https://api.github.com/repos/gaearon/redux/issues/176/labels{/name}","comments_url":"https://api.github.com/repos/gaearon/redux/issues/176/comments","events_url":"https://api.github.com/repos/gaearon/redux/issues/176/events","html_url":"https://github.com/gaearon/redux/issues/176","id":90685360,"number":176,"title":"Transducers in Redux","user":{"login":"gaearon","id":810438,"avatar_url":"https://avatars.githubusercontent.com/u/810438?v=3","gravatar_id":"","url":"https://api.github.com/users/gaearon","html_url":"https://github.com/gaearon","followers_url":"https://api.github.com/users/gaearon/followers","following_url":"https://api.github.com/users/gaearon/following{/other_user}","gists_url":"https://api.github.com/users/gaearon/gists{/gist_id}","starred_url":"https://api.github.com/users/gaearon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gaearon/subscriptions","organizations_url":"https://api.github.com/users/gaearon/orgs","repos_url":"https://api.github.com/users/gaearon/repos","events_url":"https://api.github.com/users/gaearon/events{/privacy}","received_events_url":"https://api.github.com/users/gaearon/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/gaearon/redux/labels/discussion","name":"discussion","color":"fbca04"},{"url":"https://api.github.com/repos/gaearon/redux/labels/future%20idea","name":"future idea","color":"434343"},{"url":"https://api.github.com/repos/gaearon/redux/labels/help%20wanted","name":"help wanted","color":"159818"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":12,"created_at":"2015-06-24T14:08:26Z","updated_at":"2015-07-05T07:04:20Z","closed_at":null,"body":"Reusing Store functionality is something Flux has traditionally been very bad at, due to Stores not being composable. For example, I do silly things in [Flux React Router Example](https://github.com/gaearon/flux-react-router-example) just to [reuse the pagination code in different Stores](https://github.com/gaearon/flux-react-router-example/blob/master/scripts/utils/PaginatedStoreUtils.js).\r\n\r\nThe reason for this being hard in Flux is because each Store is an event emitter, and composing event emitters is hard. But in Redux, “Stores” ([reducers, really!](https://github.com/gaearon/redux/pull/140)) are pure functions, and pure functions are easy to compose!\r\n\r\n[This gist](https://gist.github.com/gaearon/d77ca812015c0356654f) shows how reducers can be easily composed. But it's still manual composition. It's expressive, but there is some boilerplate for repetitive tasks. For example, you might want to implement things like **optimistic updates**, **undo/redo** or **pagination** scoped to specific parts of your state, but then you can't do it just once on the top—you need to repeat this logic in every relevant reducer.\r\n\r\nHigher order functions to the rescue! In fact, we *already* have a higher-order reducer: `composeStores` (to be renamed `composeReducers`). It takes your app's reducers and returns a reducer that composes their state into a single object.\r\n\r\nWe can express more patterns with higher order reducers? Absolutely! In fact this may be the beginning of **reusing logic in Flux** because these higher order reducers *don't need to know about specific actions in your app*.\r\n\r\nConsider this:\r\n\r\n```js\r\nfunction whitelistActions(reducer, actionPredicate) {\r\n  return (state, action) => actionPredicate(action) ? reducer(state, action) : state;\r\n}\r\n```\r\n\r\nIt's an example higher order reducer. Its purpose is a performance optimization by “cutting off” some expensive reducers from some unrelated actions.\r\n\r\n```js\r\nconst reducer = composeReducers({\r\n  entities: composeReducers(entitiesReducers),\r\n\r\n  // performance optimization!\r\n  editor: whitelistActions( \r\n    composeReducers(editorReducers),\r\n    action => action.type.startsWith('EDITOR_')\r\n  )\r\n});\r\n```\r\n\r\nThis is just *an example* of what higher-order reducers can do. They're not tied to your particular app. You can pass particular action types as parameters to them so they know *which precisely* actions to handle.\r\n\r\nYou can user higher-order reducers to implement things like:\r\n\r\n* undo/redo\r\n* pagination\r\n* optimistic updates\r\n* performance optimization\r\n* throttling actions\r\n* etc\r\n\r\nin a generic way, reusable across the Redux ecosystem.\r\n\r\nAnd guess what? We didn't invent higher-order reducers! **They are precisely the same thing as [transducers in ClojureScript](http://jlongster.com/Transducers.js--A-JavaScript-Library-for-Transformation-of-Data).**\r\n\r\nAnybody want to take a stab at writing some transducers for Redux?"},"comment":{"url":"https://api.github.com/repos/gaearon/redux/issues/comments/118588928","html_url":"https://github.com/gaearon/redux/issues/176#issuecomment-118588928","issue_url":"https://api.github.com/repos/gaearon/redux/issues/176","id":118588928,"user":{"login":"acdlite","id":3624098,"avatar_url":"https://avatars.githubusercontent.com/u/3624098?v=3","gravatar_id":"","url":"https://api.github.com/users/acdlite","html_url":"https://github.com/acdlite","followers_url":"https://api.github.com/users/acdlite/followers","following_url":"https://api.github.com/users/acdlite/following{/other_user}","gists_url":"https://api.github.com/users/acdlite/gists{/gist_id}","starred_url":"https://api.github.com/users/acdlite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/acdlite/subscriptions","organizations_url":"https://api.github.com/users/acdlite/orgs","repos_url":"https://api.github.com/users/acdlite/repos","events_url":"https://api.github.com/users/acdlite/events{/privacy}","received_events_url":"https://api.github.com/users/acdlite/received_events","type":"User","site_admin":false},"created_at":"2015-07-05T07:04:20Z","updated_at":"2015-07-05T07:04:20Z","body":"I made a library to create reducers from transducers:\r\n\r\nhttps://github.com/acdlite/redux-transduce\r\n\r\nIt supports transducers that conform to the [transducer protocol](https://github.com/cognitect-labs/transducers-js) used by [transducers.js](https://github.com/jlongster/transducers.js) and [transducers-js](https://github.com/cognitect-labs/transducers-js). I did discover a caveat, though (quoting from the README):\r\n\r\n> Transducers typically operate on collections. It's possible to use transducers to transform asynchronous streams, but it requires the use of local state that persists over time. We can't do this, because Redux makes a hard assumption that the reducer is a pure function -- it must return the same result for a given state and action, every time.\r\n\r\n> For this reason, transduce() transforms actions one at a time. That means transducers like filter() and map() work fine, but take() and dedupe() do not.\r\n\r\nI'm not sure if this limits its usefulness prohibitively. On the plus side, I finally understand transducers :D"}}