WWH: What? Why? How?
- What: a quick (hopefully, useful to real world) guide to functional programing using JavaScript strongly based on most adequate book.
- Why: it might empower you to write more robust programs: reusable, shorter, easier to reason about, less prone to error among others.
- How: by providing a quick textual introduction (WWH) followed by a simple code example and when possible a real code example.
Intro :: concepts
Functional Programing
What: a way to build code in which you use functions as the main design tool.
Why: might lead to code that’s easier to test, debug, parallelize, and understand.
How: thinking about what programs should do instead of how, using functions as the major unit to solve problems on computer.
First Class Functions
What: “functions are like any other data type and there is nothing particularly special about them – they may be stored in arrays, passed around, assigned to variables.”
Why: use functions to compose programs in a style that you can easily reason about, maintain, reuse and grow.
How: just create and use functions to solve problems.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// set to vars | |
var hi = function(name) { | |
return 'Hi ' + name | |
} | |
// rebound to other var | |
var greeting = hi | |
// an array of fns | |
var greetings = [hi, greeting] | |
// passed as arguments | |
var execIn1Second = function(fn) { | |
setTimeout(fn, 1000) | |
} |
Pure Functions
What: “a function that, given the same input, will always return the same output and does not have any observable side effect.”
Why: with pure functions we can easily cache, debug, test and parallelize the processing of them. There is no state to understand / set up.
How: write functions that does not have side effect. Although we’ll eventually write programs that mutate values, we can certainly try to minimize it. (And when we do need to mutate values, we can use functions to help us)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// impure – because you can change promo value (side effect) | |
// and for the same input X it can produce a different output. | |
var promo = 40 | |
var isPromo = function(price) {return price === promo} | |
// pure | |
var isPromo = function(price) {return price === 40} |
Basic toolbox :: currying
What: “You can call a function with fewer arguments than it expects. It returns a function that takes the remaining arguments.”
Why: you can promote the reusability to function level, you can use them to compose programs that expects another function
How: build a function with n parameters that returns n functions instead of the immediate result.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// an ordinary function | |
var sum = function(a, b){return a+b} | |
sum(1, 3) // 4 | |
// a curried version of that sum | |
var curriedSum = function(a){ | |
return function(b){return a+b} | |
} | |
var plusOne = curriedSum(1) // function(b) {return a+b} | |
plusOne(3) // 4 | |
plusOne(1) // 2 -> plusOne is useful outside it's original scope | |
// a simple function to curry any other function | |
// we'll use this in future | |
// DO NOT USE THIS IN PRODUCTION | |
var curry = function(uncurriedFn){ | |
var argumentsCall = [] | |
return function curriedFn(){ | |
var args = Array.prototype.slice.call(arguments) | |
if (args.length > 0) { | |
argumentsCall = argumentsCall.concat(args) | |
if (uncurriedFn.length == argumentsCall.length) { | |
return uncurriedFn.apply(this, argumentsCall) | |
} | |
} | |
return curriedFn | |
} | |
} | |
// an usage example of curry | |
var curriedAjax = curry(function(method, path){ | |
return $.ajax(path, {method: method}) | |
}) | |
var ajaxGET = curriedAjax('GET') | |
var allUsers = ajaxGET('/users') | |
var allStars = ajaxGET('/stars') |
Medium toolbox :: composing
What: is the act of creating your programs using lots of functions.
Why: this promotes the reuse at a great level and forces you to think about what instead of how.
How: chain functions to produce a new callable function.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// a simple function to compose a function | |
// DO NOT USE THIS IN PRODUCTION | |
// you can skip the reading of this function if you want | |
var compose = function(){ | |
var fns = Array.prototype.slice.call(arguments) | |
return function composed(){ | |
var thisCall = this | |
var args = Array.prototype.slice.call(arguments) | |
fns.reverse().forEach(function(fn){ | |
if (Object.prototype.toString.call(args) !== '[object Array]') args = [args] | |
args = fn.apply(thisCall, args) | |
}) | |
return args | |
} | |
} | |
// take a list of Strings and return a list of up case strings | |
var toUpperCase = function(list) { | |
return list.map(function(x){return x.toUpperCase()}) | |
} | |
// take a list of Strings and return a list of length | |
var length = function(list) { | |
return list.map(function(x){return x.length}) | |
} | |
// take a list of Integer and return the sum | |
var sum = function(list) { | |
var sum = 0 | |
list.forEach(function(x){sum += x}) | |
return sum | |
} | |
// chain all these functions and produce a new | |
var sumCharsOnList = compose(sum, length, toUpperCase) | |
sumCharsOnList(["NX", "PS4", "XboxOne"]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// brief explanation of what happens here | |
var sumCharsOnList = compose(sum, lenght, toUpperCase) | |
sumCharsOnList(["NX", "PS4", "XboxOne"]) | |
// –> first the list ["NX", "PS4", "XboxOne"] is passed to the function toUpperCase | |
// –> toUpperCase returns another list and it's the input for the lenght function | |
// –> the lenght function return a list of integers which will become the input for sum function | |
// –> sum function will reduce the list summing all the integers and returning the sum |
Example :: motivational
What: a better example to motivate you to go further with functional programing.
Why: most near real world examples are great to motivate you to learn something.
How: since you can see all the concepts together, I think you’ll notice the value.
You can see the example running at https://jsfiddle.net/swmrmgur/2/ and check the commented code down bellow.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// _ is an instance of Ramda | |
// $ is an instance of jQuery | |
// This is an app extracted from the book | |
// mostly-adequate-guide/content/ch6.html | |
// it's query the flickr API's and mount | |
// a lots of images on the page | |
// IMPURE functions ahead | |
// takes a function (callback) and returns another | |
// function which takes an url | |
var getJSON = _.curry(function(callback, url) { | |
$.getJSON(url, callback); | |
}) | |
// takes a selector and returns a functions | |
// which you can pass an html | |
var setHtml = _.curry(function(sel, html) { | |
$(sel).html(html); | |
}) | |
// PURE, good and reliable functions ahead | |
// just create an image for a given url | |
var img = function(url) { | |
return $('<img />', { | |
src: url, | |
}); | |
}; | |
// just create an url for a given query | |
var url = function(t) { | |
return 'http://api.flickr.com/services/feeds/photos_public.gne?tags=' + | |
t + '&format=json&jsoncallback=?'; | |
}; | |
// _.prop is a curried function which takes | |
// a property name and then an object | |
// ex: var getPrice = _.prop('price') | |
// getPrice({price: 3, x: "y"}) // returns 3 | |
// takes an object extracts its media and | |
// then from the media it extracts the m property | |
var mediaUrl = _.compose(_.prop('m'), _.prop('media')); | |
// it takes a media url and create an image | |
var mediaToImg = _.compose(img, mediaUrl); | |
//it takes an object and extracts items from it | |
// map each one to an image | |
var images = _.compose(_.map(mediaToImg), _.prop('items')); | |
// it takes images and set | |
// them on the document.body | |
var renderImages = _.compose(setHtml('body'), images); | |
// given a string, it'll create an url and | |
// gets the json and render the images | |
var app = _.compose(getJSON(renderImages), url); | |
// given a query it'll create a page | |
app('cats'); | |
// I strongly recommend you to read again from app to mediaUrl |
Advanced toolbox & conclusion
I hope you might see the benefits you can have from using one or other technique from functional programming but for sure there are other benefits not shown here, I strongly recommend you to read the INCREDIBLE free book (gitbook) “Professor Frisby’s Mostly Adequate Guide to Functional Programming”, in fact, most of the ideas and examples here are from it.
There are advanced techniques to deal with data mutation with less pain, to handle errors and exceptions without try and catch and more abstractions that can help you and you can read them on the book.
And don’t use the handcrafted curry and compose built here (they’re far from production-ready), instead use a library like Ramda, which provides many basic functions like: map, filter and other all of them already curried, or lodash-fp.
Yeah, there no monado here. A special thank to Daniel Martins and Juarez Bochi, they helped a lot.