Skip to content

Commit 5b9a8b0

Browse files
committed
Fix compose() to have a less surprising API
1 parent 982ab86 commit 5b9a8b0

File tree

4 files changed

+29
-26
lines changed

4 files changed

+29
-26
lines changed

docs/api/compose.md

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
# `compose(...functions)`
22

3-
Composes functions from left to right.
3+
Composes functions from right to left.
44

55
This is a functional programming utility, and is included in Redux as a convenience.
66
You might want to use it to apply several [store enhancers](../Glossary.md#store-enhancer) in a row.
77

88
#### Arguments
99

10-
1. (*arguments*): The functions to compose. Each function is expected to accept a function as an argument and to return a function.
10+
1. (*arguments*): The functions to compose. Each function is expected to accept a single parameter. Its return value will be provided as an argument to the function standing to the left, and so on.
1111

1212
#### Returns
1313

14-
(*Function*): The final function obtained by composing the given functions from left to right.
14+
(*Function*): The final function obtained by composing the given functions from right to left.
1515

1616
#### Example
1717

@@ -39,20 +39,16 @@ if (process.env.NODE_ENV === 'production') {
3939
require('redux-devtools').devTools(),
4040
require('redux-devtools').persistState(
4141
window.location.href.match(/[?&]debug_session=([^&]+)\b/)
42-
),
43-
createStore
44-
);
42+
)
43+
)(createStore);
4544

46-
// Same code without the compose helper:
45+
// Same code without the `compose` helper:
4746
//
48-
// finalCreateStore =
49-
// applyMiddleware(middleware)(
50-
// devTools()(
51-
// persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))(
52-
// createStore
53-
// )
54-
// )
55-
// );
47+
// finalCreateStore = applyMiddleware(middleware)(
48+
// require('redux-devtools').devTools()(
49+
// require('redux-devtools').persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))()
50+
// )
51+
// )(createStore);
5652
}
5753

5854
let store = finalCreateStore(reducer);

src/utils/applyMiddleware.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default function applyMiddleware(...middlewares) {
2727
dispatch: (action) => dispatch(action)
2828
};
2929
chain = middlewares.map(middleware => middleware(middlewareAPI));
30-
dispatch = compose(...chain, store.dispatch);
30+
dispatch = compose(...chain)(store.dispatch);
3131

3232
return {
3333
...store,

src/utils/compose.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
/**
2-
* Composes functions from left to right.
2+
* Composes single-argument functions from right to left.
33
*
4-
* @param {...Function} funcs - The functions to compose. Each is expected to
5-
* accept a function as an argument and to return a function.
6-
* @returns {Function} A function obtained by composing functions from left to
7-
* right.
4+
* @param {...Function} funcs The functions to compose.
5+
* @returns {Function} A function obtained by composing functions from right to
6+
* left. For example, compose(f, g, h) is identical to x => h(g(f(x))).
87
*/
98
export default function compose(...funcs) {
10-
return funcs.reduceRight((composed, f) => f(composed));
9+
return arg => funcs.reduceRight((composed, f) => f(composed), arg);
1110
}

test/utils/compose.spec.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,23 @@ import { compose } from '../../src';
33

44
describe('Utils', () => {
55
describe('compose', () => {
6-
it('composes functions from left to right', () => {
6+
it('composes from right to left', () => {
7+
const double = x => x * 2;
8+
const square = x => x * x;
9+
expect(compose(square)(5)).toBe(25);
10+
expect(compose(square, double)(5)).toBe(100);
11+
expect(compose(double, square, double)(5)).toBe(200);
12+
});
13+
14+
it('composes functions from right to left', () => {
715
const a = next => x => next(x + 'a');
816
const b = next => x => next(x + 'b');
917
const c = next => x => next(x + 'c');
1018
const final = x => x;
1119

12-
expect(compose(a, b, c, final)('')).toBe('abc');
13-
expect(compose(b, c, a, final)('')).toBe('bca');
14-
expect(compose(c, a, b, final)('')).toBe('cab');
20+
expect(compose(a, b, c)(final)('')).toBe('abc');
21+
expect(compose(b, c, a)(final)('')).toBe('bca');
22+
expect(compose(c, a, b)(final)('')).toBe('cab');
1523
});
1624
});
1725
});

0 commit comments

Comments
 (0)