Functional Programming

Robot picture

Created by Hook Studios

Functional Programming

  • What is Functional Programming?
  • Core Concepts
    • First class functions
    • Higher-order Functions
    • Pure Functions
    • Closures
  • Basic Practies
    • Map
    • Filter
    • Reduce
    • Currying
    • Functors
    • Composition
  • Lilly Inception Refactor
  • Frameworks
  • Libraries
  • Why bother with Functional Programming?
  • Functional Programming vs Object-Oriented
  • Conclusion / recap

What is Functional Programming?

refactor image

Functional Programming

Core Concepts

Apple Core

Core Concepts:

First-Class functions

"An entity which supports all the operations generally available to other entities” such as

  • Being passed as an argument
  • Being returned from a function
  • Being assigned to a variable
  • In other words: An object that also happens to be a function.

First-Class Functions

							
// Function declaration. Okay but can't be nested within non-function blocks
// The whole thing gets hoisted at compile time. (beware)
function double(x) {
	return x * x;
}
							
						
							
// Function expression, first class! 'var double = undefined' gets hoisted
// but the definition itself does not. (so chill)
var double = function(x) {
	return x * x;
}
							
						
							

// function can be stored in an array, first class!
var myArray = [5, 'socks', {a: 3}, double, false, Nan, []];
							
						
							
var nums = [1, 1, 2, 3, 5, 8];

// functon can be passed as an argument, first class!
var sum = nums.map(double); // [1, 1, 4, 9, 25, 64]
							
						

First class functions are supported by

  • Python
  • Go
  • Rust
  • C#
  • Javascript
  • All functional languages (Scheme, Haskell, Scala)
  • ...and many more

Core Concepts:

Higher-Order functions

  • Functions that can receive other functions as arguments OR return functions OR both.
  • Functions that operate on other functions are called higher-order functions.
  • Higher-order functions can be used to generalize many algorithms that regular functions cannot easily describe.
  • "Being able to write what we want to do instead of how we want to do it means we are working at a higher level of abstraction. In practice this means shorter, clearer and more pleasant code." -Marijn Haverbeke, Eloquent JavaScript

Higher-Order Functions

							
function or(p1, p2) {
	return function(x) {
		return p1(x) || p2(x);
	}
}

function negative(x) {
	return x < 0;
}

function positive(x) {
	return x > 0;
}

var nonzero = or(negative, positive);
console.log(nonzero(-5)); //true
console.log(nonzero(0)); //false
console.log(nonzero(5)); //true
							
						

Popular higher order functions

  • Map
  • Filter
  • Reduce

Core Concepts:

Pure functions

Pure Michigan Image

What's a side effect?

  • Changing the file system
  • Mutating outside variables
  • Getting user input
  • Accessing system state
  • Adding a new variable to a data structure
							
let array = [1, 2, 3, 4, 5];
							
						
							
// pure
array.slice(0, 2);
console.log(`[${array}]`); // [1,2,3,4,5]

array.slice(0, 2);
console.log(`[${array}]`); // [1,2,3,4,5]

array.slice(0, 2);
console.log(`[${array}]`); // [1,2,3,4,5]
							
						
							
// impure
array.splice(0, 2);
console.log(`[${array}]`); // [3,4,5]

array.splice(0, 2);
console.log(`[${array}]`); // [5]

array.splice(0, 2);
console.log(`[${array}]`); // []

							
						
Pulp Fiction reference

Core Concepts:

Closures

Closure: A loveable concept

  • Only accessible to a specific returning function.
  • Place nicely with pure functions.

Closures

							
let add = function(a){
  return function(b){
    return a + b;
  }
};
							
						
							
// the variable 'a' is enclosed and
// is only accessible to the returning function.

let add10 = add(10);
let result = add10(20);

console.log(result);
							
						

Functional Programming

Basics Practices

Functional Programming diagram

The Basics

map()

							
let array = [0, 1, 2, 3, 4, 5];

// a basic map function
array.map((element, index) => {
  console.log(`Element: ${element} 
`); console.log(`Index: ${index}
`); }); // for loop comparison for(let i = 0; i < array.length; i++) { console.log(`Element: ${array[i]}
`); console.log(`Index: ${i}
`); }; // index is optional! // a basic map function array.map((element) => { console.log(`Element: ${element}
`); });

The Basics

map()

							
const map = (fn, value) => value.map(fn);
							
						

The Basics

filter()

Array.filter() calls a provided callback once for each element in the array and constructs a new array of all the values for which the callback returns ‘true’ or a value that coerces to true. Only invoked for indexes which have assigned values.

Filter()

							
var customers = [
  {
    name: 'John',
    phone: '(877)4432-12'
  }, 
  {
    name: 'Anne',
    phone: '323-847-1590'
  },
  {
    name: 'Daphne',
    phone: '+1 23 5461'
  },
  {
    name: 'Howard',
    phone: '13607034562'
  }
]
          
        
Filter()
							
var reachable = function() {
  var valid = [];
    for (var i = 0; i < customers.length; i++) {
      var sanitizedPhone = customers[i].phone.replace(/\D/g, '');
      if(sanitizedPhone.length === 10 || sanitizedPhone.length === 11) {
        valid.push(customers[i]);
        }
      }
      return valid;
};
							
						
							
var reachable = customers.filter(function(person) {
  var sanitizedPhone = person.phone.slice().replace(/\D/g, '');
  return sanitizedPhone.length === 10 || sanitizedPhone.length === 11;
});
							
						
							
var hasValidPhoneNumber = function(person) {
  var sanitizedPhone = person.phone.slice().replace(/\D/g, '');
  return sanitizedPhone.length === 10 || sanitizedPhone.length === 11;
}

var reachable = customers.filter(hasValidPhoneNumber);
							
						

The Basics

reduce()

							
arr.reduce(callback [, initialValue]);

							
						
							
var numbers = [1,2,3,4];

// sum up all the values of an array
numbers.reduce(function(x,y) {
  return x+y
}, 0); // 10

// es6
numbers.reduce ((x, y) => x + y); // 10

// find the largest number
numbers.reduce((x, y) => Math.max(x, y), 0); // 4

							
						

Have you heard of Ramda or currying?

Goku eating some curry

Not quite....

The Basics

Currying

							
// normal - regular addition
let add = (a, b) => a + b;

// let add = function(a, b) {
//   return a + b;
// };

console.log(`Normal JS Invocation: ${add(1, 2)}`); // 3
console.log(`Normal JS Invocation: ${add(1, 2, 'IGNORE ME')}`); // 3
console.log(`Normal JS Invocation: ${add(1)}`); // NaN

							
						

Currying

							
// curried - curried addition
// a curried function is one where multiple arguemnts are described by a
// series of one-argument functions.
let addCurried = a => b => a + b;

// let addCurried = function(a) {
//   return function(b) {
//     return a + b;
//   };
// };

let cAdd = addCurried(1);

console.log(`Curried: ${cAdd(2)}`); // 3
console.log(`Curried: ${cAdd(2, 'IGNORE ME')}`); // 3

// ramda - makes stuff easier
let ramdaCurried = R.curry(add);

let rAdd = ramdaCurried(1);

console.log(`Curried: ${rAdd(2)}`); // 3
console.log(`Curried: ${rAdd(2, 'IGNORE ME')}`); // 3
							
						

Currying

							
let objects = [{ id: 1 }, { id: 2 }, { id: 3 }];

console.log(`w/o Curry: [${objects.map(o => o.id)}]`); // [1, 2, 3]

let get = R.curry((property, object) => {
  return object[property];
});

console.log(`w/ Curry: [${objects.map(get('id'))}]`); // [1, 2, 3]
							
						
							
// Take it even further!
let map = R.curry((fn, value) => value.map(fn));

let getIDS = map(get('id'));

console.log(`Super Ultra Functional: [${getIDS(objects)}]`); // [1, 2, 3]
							
						
Arrow Functions

The Basics

Functors

We want the funk

Not to be confused with funk-sters

Functors

  • Functors map between categories.
  • They can be thought of as functions that lift values out of a container, morph them, and then put them into a new container.
  • The first input is a morphism for the type and the second input is the container.
							
// Note that the type signature for functors looks like so

// my functor :: (a -> b) -> f a -> f b

							
						

Functors

We already have one functor: map(). It grabs the values within the container, an array, and applies a function to it.

							
var nums = [1, 4, 9];

nums.map(Math.sqrt); // [1, 2, 3]

							
						

However, we'll need to write it as a global function and not as a method of the array object. This will allow us to write cleaner, safer code later on.

							

// map :: (a -> b) -> [a] -> [b]
var map = function(f, a) {
  return arr(a).map(func(f));
}

							
						

The Basics

Composition

  • Function Composition: The combination of simple functions to build more sophisticated ones.
  • Composing functions allows us to build complex functions from many simple, generic functions.
  • By treating functions as building blocks for other functions, we can build truly modular applications with excellent readability and maintainability.

Composition

							
// function that combines two functions together:
// run function 'f' on the result of function 'g'
var compose = function(f, g) {
  return function(x) {
	  return f(g(x));
  };
};

// ES6 hyper-terse equivalent
const compose = (f, g) => (x) => f(g(x));

const upperCase = (str) => str.toUpperCase();
const trim = (str) => str.replace(/^\s+I\s+$/g, '');
const replaceIWithBang = (str) => str.replace(/[i]/g, '!');

const labelMaker = compose(upperCase, trim);
// Ramda's built-in compose method takes any number of functions and
// will return them right-to-left
const coolLabelMaker = R.compose(upperCase, replaceIWithBang, trim);

let labels = [' iconic ', 'high five    ', ' bim bap'].map(labelMaker);
let coolLabels = [' iconic ', 'high five    ', ' bim bap'].map(coolLabelMaker);

console.log(labels); // [ 'ICONIC', 'HIGH FIVE', 'BIM BAP']
console.log(coolLabels); // ["!CON!C", "H!GH F!VE", "B!M BAP"]

							
						

Composing in the wild

Generating a valid HTTP request using an oauth signature

  • request method (GET or POST)
  • base url ('https://api.twitter.com/1/statuses/update.json')
  • request parameters (status="Best tacos")
  • oauth parameters (consumer key, method, timestamp, token, oauth version)
    • Percent encode every key and value that will be signed
    • Sort the list of parameters alphabetically by encoded key
    • For each key/value pair:
      • append encoded key to the output string
      • Append the '=' character to the output string.
      • If there are any key/value pairs remaining, append the '&' character to the output string.

Functional Programming

Lilly Inception Refactor

Im gonna need those TPS reports Thanks

Canvas.js - original version

							
'use strict';

var createjs = require('createjs');

var canvas;
var stage;
var exportRoot;

// set canvas id
var Canvas = function() {
  canvas = document.getElementById('you-line');
};

// initialize canvas animation
Canvas.prototype.init = function() {
  exportRoot = new lib02.Youtube_Beacon05_Lily_970x250();
  stage = new createjs.Stage(canvas);
  stage.addChild(exportRoot);
  stage.update();
  stage.enableDOMEvents(false);
};

// start canvas animation
Canvas.prototype.start = function() {
  canvas.style.opacity = 1;
  createjs.Ticker.setFPS(lib02.properties.fps);
  createjs.Ticker.addEventListener('tick', stage);
};

module.exports = Canvas; //29 lines
							
						

Canvas.js - refactored

							
'use strict';

import createjs from 'createjs';

export default function() {
  const youLine = document.getElementById('you-line');
  const exportRoot = new lib02.Youtube_Beacon05_Lily_970x250();
  const stage = new createjs.Stage(youLine);
  const canvas = Object.create({});

  canvas.init = () => {
    stage.addChild(exportRoot);
    stage.update();
    stage.enableDOMEvents(false);
  };

  canvas.start = () => {
    youLine.style.opacity = 1;
    createjs.Ticker.setFPS(lib02.properties.fps);
    createjs.Ticker.addEventListener('tick', stage);
  };

  return canvas;
}; //23 lines - 20% reduction

							
						

Loader.js - original version

							
'use strict';

function Loader( manifest ) {
  this.manifest = manifest;
  this.loadItems = 0;
  this.loadItemsTotal = 0;
  this.loadComplete = false;
  this.consoleObject = {
    loaded: 0.0
  };

  manifest.forEach(function(item) {
    this._loadImage(item.src);
  }.bind(this));

  this._loadScript('js/vendor.js');
}

Loader.prototype._loadImage = function( path ) {
  this.consoleObject[path] = false;
  var img = new Image();
  img.addEventListener( 'load', this._onLoad.bind( this, path ) );
  img.src = path;
  this.loadItemsTotal++;
};

Loader.prototype._loadScript = function( path ) {
  this.consoleObject[path] = false;
  global.Enabler.loadScript( global.Enabler.getUrl( path ), this._onLoad.bind( this, path ) );
  this.loadItemsTotal++;
};

Loader.prototype._onLoad = function ( id ) {
  this.loadItems++;
  this.consoleObject[id] = true;
  this.consoleObject.loaded = Math.round( ( this.loadItems / this.loadItemsTotal ) * 100 ) + '%';
  if ( this.loadItemsTotal === this.loadItems ) {
    this._onLoadComplete();
  }
};

Loader.prototype._onLoadComplete = function () {
  if (this.loadComplete === true) {
    return;
  }

  this.loadComplete = true;
  this._loadScript('js/main.build.js');
};

module.exports = Loader; //50 lines of code
							
						

Loader.js - refactored

							
'use strict';

export default function(manifest) {

  //Loads all the images in the manifest
  const loadImage = function(path) {
    return new Promise((resolve, reject) => {
      let img = new Image();
      img.addEventListener('load', resolve);
      img.addEventListener('error', reject);
      img.src = path.src;
    });
  };

  //loads vendor and main.build scripts, individually
  const loadScript = function(path) {
    return new Promise((resolve, reject) => {
      global.Enabler.loadScript(global.Enabler.getUrl(path), resolve);
    });
  };

  const loadImagePromise = manifest.map(loadImage);
  const loadVendor =  loadScript('js/vendor.js');
  const loadMain = () => loadScript('js/main.build.js');

  //After all the images have loaded, then load in vendor, lastly load mainbuild.js
  Promise.all(loadImagePromise.concat(loadVendor)).then(loadMain);
}; //24 lines of code - 52% reduction.


							
						

Load.js - original

							

'use strict';

var config = require('../config');
var Loader = require('./Loader');

// set up youtube iframe api, add function to global scope
var Bootstrap = function () {

  this.tag = document.createElement('script');
  this.tag.src = 'https://www.youtube.com/iframe_api';
  this.firstScriptTag = document.getElementsByTagName('script')[0];
  this.firstScriptTag.parentNode.insertBefore(this.tag, this.firstScriptTag);

  window.onYouTubeIframeAPIReady = function() {
    this.onYouTubeLoaded();
  }.bind(this);

};

// once youtube is loaded, initialize enabler
Bootstrap.prototype.onYouTubeLoaded = function () {

  if (Enabler.isInitialized()) {
    this.onInitialize();
  } else {
    Enabler.addEventListener(studio.events.StudioEvent.INIT, this.onInitialize.bind(this));
  }

};

// once the page is initialized, check that the page is loaded and fire pageLoadedHandler
Bootstrap.prototype.onInitialize = function () {

  if (Enabler.isPageLoaded()) {
    this.pageLoadedHandler();
  } else {
    Enabler.addEventListener(studio.events.StudioEvent.PAGE_LOADED, this.pageLoadedHandler.bind(this));
  }

};

// make ad visible
Bootstrap.prototype.pageLoadedHandler = function () {

  if (Enabler.isVisible()) {
    this.adVisibleHandler();
  } else {
    Enabler.addEventListener(studio.events.StudioEvent.VISIBLE, this.adVisibleHandler.bind(this));
  }

};

// create ad
Bootstrap.prototype.adVisibleHandler = function () {

  var loader = new Loader(config.manifest);

};

module.exports = Load; //59 lines of code
							
						

Load.js - refactored

							
'use strict';

import { config } from './config';
import Loader from './core/Loader';

// Load the YouTube API, insert the script tag and assign the window object.
const loadYTApi = () => {

  const tag = document.createElement('script');
  tag.src = 'https://www.youtube.com/iframe_api';
  const firstScriptTag = document.getElementsByTagName('script')[0];
  firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

  return new Promise( (resolve, reject) => {
    window.onYouTubeIframeAPIReady = resolve;
  });

};

// Returns a promise that resolves if the method is true, otherwise assigns an event listener.
const enablerCheck = (method, state) => {

  return new Promise( (resolve, reject) => {
    return method() ? resolve() : Enabler.addEventListener(state, resolve);
  });

};

// Load the manifest once the Ad is visible.
const adVisibleHandler = () => {
  const loader = Loader(config.manifest);
};

// Enabler promises for initialize, load and visible states.
const initPromise = enablerCheck(Enabler.isInitialized.bind(Enabler), studio.events.StudioEvent.INIT);
const loadPromise = enablerCheck(Enabler.isPageLoaded.bind(Enabler), studio.events.StudioEvent.PAGE_LOADED);
const visiblePromise = enablerCheck(Enabler.isVisible.bind(Enabler), studio.events.StudioEvent.VISIBLE);

// If everything is a-ok, show the Ad.
Promise.all([loadYTApi(), initPromise, loadPromise, visiblePromise]).then(adVisibleHandler);
//39 lines of code - 34% reduction of code

							
						

Animation.js - original version

							
'use strict';

var TweenMax = require('TweenMax');

var AnimationController = function () {

  // selectors
  this.player = document.querySelector('#player');
  this.animationWrap = document.querySelector('#animation');
  this.loader = document.querySelector('#loading');
  this.whiteBox = document.querySelector('#white-box');
  this.lillyText = document.querySelector('#lilly-text');
  this.fansText = document.querySelector('#fans-text');
  this.slash = document.querySelector('#slash');
  this.underlay = document.querySelector('#underlay');
  this.lozenge = document.querySelector('#lozenge');

  // booleans
  this.endOnce = true;

  this.init();

};

// initialize controller, build all timelines.
AnimationController.prototype.init = function() {
  this.globalTimeline = new TimelineMax({paused: true});
  this.globalTimeline.add(this.tl1(), 0.0);
  this.globalTimeline.add(this.tl2(), 21.25);
  return this.globalTimeline;
};

// timeline 0 - loading  wipe animation
AnimationController.prototype.tl1 = function () {
  this.tl1 = new TimelineMax();
  this.tl1.to([this.underlay, this.loader], 0.4, {autoAlpha: 0, ease: Power1.easeOut});
  return this.tl1;
};

// timeline 4 - last zoom in of branding
AnimationController.prototype.tl2 = function () {
  this.tl2 = new TimelineMax();
  this.tl2.fromTo(this.animationWrap, 3.225, {x: '-200%',
    autoAlpha: 1,
    scaleX: 10,
    scaleY: 10},
    {x: '0%',
    scaleX: 1,
    scaleY: 1,
    ease: Power3.easeOut});

  return this.tl2;
};

// make player visible, set animation wrap and fire tl0
AnimationController.prototype.start = function () {
  this.player.style.opacity = 1;
  this.globalTimeline.play();
};

// play animation
AnimationController.prototype.play = function () {
  this.globalTimeline.play();
};

// seek animation
AnimationController.prototype.seek = function(time) {
  this.globalTimeline.seek(time);
};

// pause animation
AnimationController.prototype.pause = function () {
  this.globalTimeline.pause();
};

// kill all animations, end banner
AnimationController.prototype.endState = function () {
  if (this.endOnce) {
    TweenMax.killAll(true, true, true, true);
    TweenMax.set(this.animationWrap, {autoAlpha: 1});
    this.endOnce = false;
  }
};

module.exports = AnimationController; //84 lines of code
							
						

Animation.js - refactored

							
'use strict';

import TweenMax from 'TweenMax';

export default function() {
    // selectors
    const player = document.querySelector('#player');
    const animationWrap = document.querySelector('#animation');
    const loader = document.querySelector('#loading');
    const whiteBox = document.querySelector('#white-box');
    const lillyText = document.querySelector('#lilly-text');
    const fansText = document.querySelector('#fans-text');
    const slash = document.querySelector('#slash');
    const underlay = document.querySelector('#underlay');
    const lozenge = document.querySelector('#lozenge');

    // timeline
    let timeline = null;

    // booleans
    let endOnce = true;

    const animation = Object.create({});

    animation.init = () => {
      timeline = new TimelineMax({paused: true});
      timeline.add(fadeOutLoader(), 0.0);
      timeline.add(animateBranding(), 21.25);
      return timeline;
    };

    function fadeOutLoader() {
      const tl = new TimelineMax();
      tl.to([underlay, loader], 0.4, {autoAlpha: 0, ease: Power1.easeOut});
      return tl;
    };

    function animateBranding() {
      const tl = new TimelineMax();
      tl.fromTo(animationWrap, 3.225, {x: '-200%',
        autoAlpha: 1,
        scaleX: 10,
        scaleY: 10},
        {x: '0%',
        scaleX: 1,
        scaleY: 1,
        ease: Power3.easeOut});
      return tl;
    };

    animation.start = () => {
      player.style.opacity = 1;
      timeline.play();
    };

    animation.play = () => timeline.play();

    animation.seek = (time) => timeline.seek(time);

    animation.pause = () => timeline.pause();

    animation.endState = () => {
      if (endOnce) {
        TweenMax.killAll(true, true, true, true);
        TweenMax.set(animationWrap, {autoAlpha: 1});
        endOnce = false;
      }
    };

    return animation;
}; //70 lines of code - 16% reduction


							
						

Functional Programming

Let's talk Frameworks

different js framework logos

Functional Progamming

React

React.js logo
  • Components provide abstractions
  • Composable Functions
  • Virtual DOM

React

							
var Timer = React.createClass({
  getInitialState: function() {
    return {secondsElapsed: 0};
  },

  tick: function() {
    this.setState({secondsElapsed: this.state.secondsElapsed + 1});
  },

  componentDidMount: function() {
    this.interval = setInterval(this.tick, 1000);
  },

  componentWillUnmount: function() {
    clearInterval(this.interval);
  },
  
  render: function() {
    return (
      
Seconds Elapsed: {this.state.secondsElapsed}
); } }); ReactDOM.render(<Timer />, mountNode);

Functional Progamming

Deku

Deku.js logo
  • "Functional alternative to React"
  • Define your UI as a tree of components
  • Virtual DOM

Deku

							
import {element} from 'deku'

function render (component) {
  let {props,state} = component
  return 
}

function afterMount(component, el, setState) {
  setState({ mounted: true })
}

export default { render, afterMount }
					
							
						
							
import {tree,render,element} from 'deku'
import Button from './button'

var app = tree()
render(app, document.body)
					
							
						
							
export let Button = {
  render({props, state}) {
    return 
  }
}
					
							
						

Functional Progamming

Vue

Vue.js logo
  • Provides reactive and composable View components
  • Uses the actual DOM as the template
  • Limited to environments where DOM is present

Vue

							
  • {{ todo.text }}
							
new Vue({
  el: '#app',
  data: {
    newTodo: '',
    todos: [
      { text: 'Add some todos' }
    ]
  },
  methods: {
    addTodo: function () {
      var text = this.newTodo.trim()
      if (text) {
        this.todos.push({ text: text })
        this.newTodo = ''
      }
    },
    removeTodo: function (index) {
      this.todos.splice(index, 1)
    }
  }
})
					
							
						

Functional Programming

Let's talk Libraries

At the library like

Functional Programming

Let's talk Libraries

As the years go, Internet Explorer 9 has faded into the past and native ES5 methods are expected for most applications. So the role of functional programming libraries has shifted from a compatibility layer to adding new, foundational methods for functional programming.

Functional Programming

Underscore

underscore logo
  • 1st Generation Library
  • Cross-browser compatibility
  • JavaScript utility library

Functional Programming

LoDash

lodash logo
  • Preferred alternative to Underscore
  • loadash/fp module
  • Generic utility belt

Functional Programming

Ramda

Ramda logo
  • 2nd generation functional programming library
  • Javascript feel
  • Lots of breaking changes, because it hasn't reached 1.0

Functional Programming

Why bother?

why bother?

Functional Programming

Functional Programming encourages functions that:

  • accept at least one argument
  • return at least one value
  • avoid mutating data ouside of its own scope

When you don't mutate global state...

You drastically reduce the likelihood of bugs

  • Functions are no longer affected by "hidden state"
  • You circumvent race conditions which occur when output is dependent on a sequence of uncontrollable events such as the network, device latency, user input or anything that occurs randomly. E.g. Google Instant
  • "Reliance upon state is one of the largest contributors to system complexity"
  • An ounce of prevention is worth a pound of
                cure

When you don't mutate global state...

Debugging becomes easier

  • There's no guesswork about how a function will perform at different points in the program.
  • The function can be easily isolated to check values

When you don't mutate global state...

Changing the code is less painful

  • Without relying on state, changes touch far less of your code
  • The code becomes easily testable
  • Unit tests are a breeze to write when a function only relies on arguments passed directly to it
  • Tests help you maintain your code and save future-you from having to remember what the code needs to do. Failed tests will remind you!
  • I should start writing unit tests

Pure functions are more reusable

  • Functional programming encourages functions that are short, simple, and do one thing
  • Function names become expressive and meainingful (e.g. "hasID" or "addsTimeStamp")
  • Short functions are easier to understand
  • Smaller functions can be reused as part of more complex tasks
  • When higher-level functions are composed of smaller functions they become more robust, less fragile.
  • An ounce of prevention is worth a pound of
                cure

Pure functions can be cached

  • When you cache using 'memoization' you get better performance
  • Recursive functions, normally too expensive, become viable

var memoize = function(f) {

  //create a cache
  var cache = {};

  return function() {
    var arg_str = JSON.stringify(arguments);

    //If we already have the answer in our cache, do nothing
    //Otherwise, store the result of this function call in our cache
    cache[arg_str] = cache[arg_str] || f.apply(f, arguments);

    return cache[arg_str];
  };
};

function fibonacci(n) {
  return (n === 0 || n === 1) ? n : fibonacci(n - 1) + fibonacci(n - 2);
};

function betterFibonacci = memoize(function(n) {
  return (n === 0 || n === 1) ? n : betterFibonacci(n - 1) + betterFibonacci(n - 2);
});

var ITERATIONS = 42;

console.time('non-memoized');
console.log(fibonacci(ITERATIONS));
console.timeEnd('non-memoized'); // 3788ms

console.time('memoized');
console.log(betterFibonacci(ITERATIONS));
console.timeEnd('memoized'); // 1ms !!!

The best part...

  • You don't have to completely abandon your current practices!
  • Functional code can live next to Object Oriented code
  • An existing code base can be updated gradually
  • Learning and using a few patterns of functional programming is a great start towards cleaner and more elegant code

Functional Programming

The Great Debate

Functional Programming vs. Object Oriented Programming

Everybody was kung-fu fighting

Functional Programming

Why FP over OO?

“The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.”
-Joe Armstrong

The End

Functional programming is a style that emphasizes and enables the writing of smarter code, which minimizes complexity and increases modularity.

fp dino

The End

JavaScript provides an excellent medium for this approach.

Happy Gage - The Buddliest Bud

Resources

hook logo