Let's start with some terminology
Declarative programming is a programming paradigm … that expresses the logic of a computation without describing its control flow.
Imperative programming is a programming paradigm that uses statements that change a program’s state.
Declarative programming is a programming paradigm in which you describe how to do something.
Imperative programming is a programming paradigm in which you describe what to do.
Not exactly, but let's not be pedantic.
Actually, let's be pedantic ...
A given function does not access the mutable state that is external to the function.
Less precisely, a function may not have side-effects.
Given the same input, a function should always return the same output.
A function call can be replaced by its return value or another referentially transparent call with the same result.
No observable side-effects. Easy to read and reason about.
Explicit arguments. Expressions are self-documenting and portable.
Cacheable. Based on arguments.
Easily testable. Assert output by input. No mocking.
Safe parallelization. No race conditions.
"In an ideal world, most of your components would be stateless functions because in the future we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations. This is the recommended pattern, when possible."
f(x)(y)(z) === f(x, y)(z) === f(x, y, z)
Allows to call a function with fewer arguments. It returns a function that takes the remaining arguments until all arguments are there.
You can choose to call it all at once or simply feed in each argument piecemeal.
function add(a, b, c, d) {
return a + b + c + d;
}
add(1, 2, 3, 4) // 10
typeof curry(add) // 'function'
typeof curry(add)(5, 2) // 'function'
curry(add)(5, 5, 5, 5) // 20
curry(add)(10)(20)(30)(40) // 100
f(a) === b
g(b) === c
g(f(a)) === c
Function breeding
Takes functions with traits you'd like to combine and mashes them together to spawn a brand new one.
First function can have any arity, all others have to be unary.
function emphasize(string) {
return string + '!';
}
function uppercase(string) {
return string.toUpperCase();
}
const yell = compose(
emphasize,
uppercase,
);
yell('hello') // 'HELLO!'
Documentation:
http://ramdajs.com/docs/
Better documentation: https://github.com/ramda/ramda/wiki/What-Function-Should-I-Use%3F
A practical functional library for JavaScript programmers.
import R from 'ramda';
const props = { state: { counter: { count: 10 }}};
const selectCount = R.path(['state', 'counter', 'count']);
selectCount(props) // 10;
// props && props.state && props.state.counter && props.state.counter.count
const props = { state: { counter: {}}};
const selectCountOr = R.pathOr(0, ['state', 'counter', 'count']);
selectCount(props) // 0;
const props = { count: 10 };
const selectCount = R.propOr(0, 'count');
selectCount(props) // 10
import R from 'ramda';
import React from 'react';
const mapProps = R.curry((transformProps, Component) =>
R.compose(Component, transformProps));
const getCurrentCount = R.pathOr(0, ['state', 'counter', 'current']);
const withCountFromState = mapProps((props) => ({
...props,
count: getCurrentCount(props),
}));
const MyCounter = (props) => (<div>Count: {props.count}</div>);
export default withCountFromState(MyCounter);
const mapProps = R.curry((transformProps, Component) =>
R.compose(Component, transformProps));
const withProps = input =>
mapProps(props => ({...props, ...input}));
const EnhancedComponent = withProps({
myExtraProp: 'some value',
})(MyComponent);
const mapProps = R.curry((transformProps, Component) =>
R.compose(Component, transformProps));
const withDefaults = input =>
mapProps(props => ({ ...input, ...props }));
const EnhancedComponent = withDefaults({
myExtraProp: 'some value',
})(MyComponent);
const mapProps = R.curry((transformProps, Component) =>
R.compose(Component, transformProps));
const isFunction = R.is(Function);
const withProps = mapProps(props => ({
...props,
...(isFunction(input) ? input(props) : input),
}));
const EnhancedComponent = withProps((props) => ({
myCalculacedExtraProps: doSomething(props),
}))(MyComponent);
const branch = R.curry((predicateFn, Fallback, Component) => R.ifElse(
predicateFn,
props => (<Component {...props} />),
props => (<Fallback {...props} />),
));
const isVisible = R.propOr(false, 'visible');
const withFallback = branch(isVisible, RenderNothing);
const EnhancedComponent = withFallback((props) => (
<div>This will only show if 'props.visible' is true.</div>
));
const withState = R.curry(
(stateName, stateUpdateFn, initialState, BaseComponent) => {
return class extends Component {
state = { value: initialState, };
stateUpdateFn = (fn) => {
this.setState(({ value }) => ({ value: fn(value) }))
}
render() {
const stateProps = {
[stateName]: this.state.value,
[stateUpdateFn]: this.stateUpdateFn,
};
return (<BaseComponent {...this.props} {...stateProps} />);
}
};
});
const withCounter = withState('count', 'setCount', 0);
const Counter = withCounter(({ count, setCount }) => (
<div>
<button onClick={() => setCount(count - 1)}>Decrement</button>
{count}
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
));
const withLifecycle = R.curry((methods = {}, fn) => createClass({
...methods,
render() {
return fn(this.props);
},
}));
const neverUpdate = withLifecycle({
shouldComponentUpdate(nextProps) {
return false; // Do never update this component.
},
});
const IdleComponent = neverUpdate(MyComponent);
const enhance = compose(
mapProps(R.omit(['foo'])),
withReducer('counter', 'dispatch', counterReducer, 10),
withDefaultProps({ something: 'foo' }),
);
const EnhancedComponent = enhance(MyComponent);
const redux = (reducers, state) => {
const listeners = [];
const reducingFns = R.mapObjIndexed((reducer, key) =>
R.useWith(reducer, [R.prop(key), R.identity]));
const update = R.converge(R.applySpec, [reducingFns]);
const updateState = update(reducers);
return {
dispatch: (action) => {
state = updateState(state, action)
R.forEach(f => f(state), listeners)
},
subscribe: (f) => {
listeners.push(f)
},
};
};