View
207
Download
0
Category
Preview:
Citation preview
Func up your codeMaciej Komorowski
What is func%onal programming?
Func%onal programming is programming paradigm [...] that
treats computa%on as the evalua%on of mathema&cal func&ons and
avoids changing-state and mutable data.
— Wikipedia
Pillars of FP
• Higher-order func0ons (e.g. map)
• Immutable data
• Lazy evalua0on
• Pure func0ons
• Recursion
Pure func)on! Always evaluates the same result value given the same arguments! Does not cause any seman5cally observable side effect, such as muta5on of mutable objects
Pure func)on examples// Purefunction add(a, b) { return a + b;}
// ImpureMath.random();
// Impureconst items = []
function addItem(item) { return items.push(item);}
State muta(on
State muta(on in OOP
! Relies heavily on encapsula)on! Has hidden nature! Brings nondeterminism! Root cause of doom
Brogrammer1
State muta)on example
A brogrammer is [...] a slang term for a macho, male programmer. A brogrammer
might self-describe as a sociable programmer.
— Wikipedia
1 Frans Hals, Oil on canvas, 1630. Source: classicprogrammerpain;ngs
What brogrammer does?
• drinkBeer
• talk
Stateful implementa*on
class Brogrammer { constructor() { this.beers = 0; }
drinkBeer() { /* ... */ } talk() { /* ... */ }}
Stateful – drinkBeer
it('increment number of drunk beers', () => { expect(brogrammer.drinkBeer()).toEqual(1); expect(brogrammer.drinkBeer()).toEqual(2);});
// Inside Brogrammer classdrinkBeer() { return this.beers += 1; // encapsulates 'beers'}
Stateful – talk testsit('says "Beer me up!" before drinking', () => { expect(brogrammer.talk()).toEqual("Beer me up!");});
it('says "Yummy" after 1st beer', () => { brogrammer.drinkBeer(); expect(brogrammer.talk()).toEqual("Yummy");});
it('says "I a<M dRuNk" after 10th beer', () => { brogrammer.drinkBeer(); // 8 x brogrammer.drinkBeer(); brogrammer.drinkBeer(); expect(brogrammer.talk()).toEqual("I a<M dRuNk");});
Stateful – talk implementa+on
// Inside Brogrammer classtalk() { return ({ 0: 'Beer me up!', 1: 'Yummy', // ... 10: 'I a<M dRuNk', })[this.beers] || '';}
How to remove state?Single source of truth
Naïve stateless – implementa+on
class Brogrammer { constructor(store) { this.store = store; }
// ...}
Naïve stateless – drinkBeer tests
it('increment number of drunk beers', () => { const store = { beers: 0 }; const brogrammer = new Brogrammer(store);
brogrammer.drinkBeer();
expect(store.beers).toEqual(1);});
Naïve stateless – drinkBeer implementa+on
// Inside Brogrammer classdrinkBeer() { return this.store.beers += 1;}
Naïve stateless – talk tests
it('says "I a<M dRuNk" after 10th beer', () => { const store = { beers: 10 }; const brogrammer = new Brogrammer(store);
expect(brogrammer.talk()).toEqual("I a<M dRuNk");});
Naïve stateless – talk implementa+on
// Inside Brogrammer classtalk() { return ({ 0: 'Beer me up!', 1: 'Yummy', // ... 10: 'I a<M dRuNk', })[this.store.beers] || '';}
Solu%on review! Removed state from the Brogrammer! Easier tes2ng" Lost encapsula-on
How to improve?The Redux way
Three Principles of Redux3
! Single source of truth! State is read-only! Changes are made with pure func)ons
3 Source: Redux
Three Principles of ReduxSingle source of truth
console.log(store.getState());
// {// brogrammer: {// beers: 0// }// }
Three Principles of ReduxState is read-only
store.dispatch({ type: 'INCREMENT_BEERS',});
Three Principles of ReduxChanges are made with pure func3ons
function brogrammer(state = {}, action) { switch (action.type) { case 'SET_BEERS': return { ...state, beers: 5, }; // case ... }}
Redux – incrementBeers ac%on
it('returns action with type INCREMENT_BEERS', () => { const action = incrementBeers();
expect(action).toEqual({ type: 'INCREMENT_BEERS' });});
Redux – incrementBeers ac%on
const incrementBeers = () => ({ type: 'INCREMENT_BEERS',});
Redux – brogrammer reducer
it('returns state with incremented beers', () => { const state = { beers: 0 }; const action = incrementBeers();
const nextState = brogrammer(state, action);
expect(nextState).toEqual({ beers: 1 });});
Redux – brogrammer reducerimplementa)on
const brogrammer = (state = {}, action) => { switch (action.type) { case 'INCREMENT_BEERS': return { ...state, beers: state.beers + 1 }; default: return state }}
Redux – drinkBeer tests
it('increment number of drunk beers', () => { const store = createStore({ beers: 0 }); const brogrammer = new Brogrammer(store);
brogrammer.drinkBeer();
expect(store.dispatch) .toHaveBeenCalledWith(incrementBeers());});
Redux – drinkBeer implementa+on
// Inside Brogrammer classdrinkBeer() { return this.store.dispatch(incrementBeers());}
Redux – talk tests
it('says "I a<M dRuNk" after 10th beer', () => { const store = createStore({ beers: 10 }); const brogrammer = new Brogrammer(store);
expect(brogrammer.talk()).toEqual("I a<M dRuNk");});
Redux – talk implementa+on
// Inside Brogrammer classtalk() { return ({ 0: 'Beer me up!', 1: 'Yummy', // ... 10: 'I a<M dRuNk', })[this.store.get('beers')] || '';}
Solu%on review! Easy tes(ng! Be,er separa(on! Explicit state changes! Full control over state
A few thoughts on Redux
Immutable data
Mutable vs immutable// Mutableconst items = ['foo'];
items.push('bar');items.push('baz');
console.log(items); // ["foo", "bar", "baz"]
Mutable vs immutable// Immutable via immutable-jsconst items = Immutable.List.of('foo');
items.push('bar');items.push('baz');
console.log(items.toArray());// ["foo"]
console.log(items.push('bar').push('baz').toArray());// ["foo", "bar", "baz"]
What about performance?5
5 Source: React.js Conf 2015 - Immutable Data and React
Performance benchmarkArray push 1,000,000 items:
var list = [];for (var i = 0; i < 1000000; i++) { list.push(i);}
83 ms
Performance benchmarkMori immutable vector conj 1,000,000 items:
var list = mori.vector();for (var i = 0; i < 1000000; i++) { mori.conj(list, i);}
288 ms
Should update problemconst form = { /* ... */ };
function submit(form) { if (hasChanged(form)) { doSomethingExpensive(form); }}
Should update problemversioning
let currentFormVersion;
function hasChanged(form) { const formVersion = md5(JSON.stringify(form));
return formVersion !== currentFormVersion;}
Should update problemdirty bits
function hasChanged(form) { for (var field in form) { if (field.meta.dirty === true) { return true; } }
return false;}
Should update problemobservable pa+ern
const form = { /* ... */ };
Object.observe(form, changes => { doSomethingExpensive(form);});
form.firstName = 'Maciej';form.lastName = 'Komorowski';form.profession = 'Brogrammer'; // Not true :)
Should update problemimmutable
let currentForm = { /* ... */ };
function hasChanged(form) { return currentForm !== form;}
Memoiza(on examplefunction memoize(fn) { var cache = {}; return function (arg) { var hash = arg === Object(arg) ? JSON.stringify(arg) // Wat? : arg; return hash in cache ? cache[hash] : (cache[hash] = fn.call(this, arg)); }}
Memoiza(on examplefunction rawSum(list) { return list.reduce((a, b) => a + b)}
const sum = memoize(rawSum);const array = [0, 1, ...1000000];
sum(array); // 89 ms
rawSum(array); // 51 mssum(array); // 42 ms
Advantages of immutability! No defensive copies! Can be faster! Be3er for concurrency (no deadlocks)
Explore func-onal programing❗It's worth it
QuizWhat f does?
f = 0 : 1 : zipWith (+) f (tail f)
AnswerGenerates Fibonacci sequence
> let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
> take 11 fibs[0,1,1,2,3,5,8,13,21,34,55]
Recommended