Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
How Functional Programing Can Avoid Broken State
(function nonFunctionalExample(){
this.breadCrumb = []; //<-- external state
this.node = {
name: "Child",
parent: {
name: "Parent"
}
};
function buildBreadCrumb( node ){
this.breadCrumb = []; //<-- modifies external state
function addBreadCrumb( _node ){
this.breadCrumb.push( _node ); //<-- modifies external state
if( !!_node.parent ) {
addBreadCrumb( _node.parent ); //<-- modifies external state
}
}
addBreadCrumb( node );
//Imagine sometime in the future a developer `throws new Error();`, the expection would leave us in a broken
//unfinished state for this.breadCrumbs because we didn't reverse. The UI is going to look funny and/or break more pushing
//this error outside the function.
//throw new Error(''); //Uncomment the error and run it in console to see the side effect.
this.breadCrumb.reverse(); //<-- modifies external state
//Mysteriously returns undefined, we have no way to validate if this function was successful and no easy way to test.
}
try{ //<- Promises behave like try/catches so this simiulates this method call in a promise.
//Is a little unclear on what state we are changing, and makes testing a little tricky because we now
//have to mock the specific state property as well.
//We also have to bind the method call to `this` since we reference external vars outside it scope. The other alternative
//is to do the `var that = this;` hack, but it is still the same idea.
buildBreadCrumb.call( this, this.node );
}catch(e){}
console.log('result 1 ->', this.breadCrumb.map( function( node ){ return node.name } ) ); //Should print out ["Parent", "Child"]
})();
(function functionalExample(){
this.breadCrumb = []; //<-- external state
this.node = {
name: "Child",
parent: {
name: "Parent"
}
};
function buildBreadCrumb( node ){
var breadCrumb = []; //<-- internal function state, modify it and return it.
function addBreadCrumb( _node ){
breadCrumb.push( _node );
if( !!_node.parent ) {
addBreadCrumb( _node.parent );
}
}
addBreadCrumb( node );
//Imagine the same `throw new Error();` is here. Since we haven't modified external state yet the exception will leave
//`this.breadCrumbs = [];` :) While the error sucks it didn't put us in a weird state like the unreversed bread crumb.
//At least this error is contained in the function and does not put us in an unexpected state that could cause more
//issues up the UI stack.
//throw new Error(); //Uncomment the error and run it in console to see how we don't have that side effect.
//Returns back a new state that we should update to. The great thing is since it returns an array
//we can validate it in our code, unit test it, and not worry about mutating external state until everything
//is set correctly.
return breadCrumb.reverse();
}
try{ //<- Promises behave like try/catches so this simiulates this method call in a promise.
//Ahhh, now I see what state is being update with this function call.
//Also notice since we don't reference external vars we don't have to bind `this`, or use `var that = this;` hacks
this.breadCrumb = buildBreadCrumb( this.node );
}catch(e){}
console.log('result 2 ->', this.breadCrumb.map( function( node){ return node.name } ) );
}.bind( {} ))();

Ahhh I see, makes sense. I struggle with recursion sometimes, specifically with doing it in a more functional manner.

Owner

craigmr commented May 28, 2015

and it's also okay for addBreadCrumb to modify the state of buildBreadCrumb since there is no leakage in state from buildBreadCrumb.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment