Skip to content

Instantly share code, notes, and snippets.

@rrag
Last active March 19, 2024 16:11
Show Gist options
  • Save rrag/7a97c7dda1e986980293 to your computer and use it in GitHub Desktop.
Save rrag/7a97c7dda1e986980293 to your computer and use it in GitHub Desktop.
Yet another tutorial and Cheat sheet to Functional programming

I started my carrer as a Rookie Java dev, I liked java. Object Oriented code was awesome, I thought it was solution to all the problems. Used to hate javascript.

As time would have it I had to learn javascript out of necessity. I started with jQuery, then node.js, d3, underscore, ES5 array methods like map, reduce, filter etc.

This new style of writing functional code started to grow on me and writing code with these concepts became the new normal. But I never got around to understanding the terminology used and what they mean, until I spent the time to learn these. I realize I had not been following most of the principles and been writing bad code whilst thinking I was writing functional style.

This introduction is for those who like me have taken the plunge to functional programming but still have not been able to swim

Terminology

Functions as first class citizens

Functions are first class means they are just like anyone else, or rather they are not special, they behave the same as say primitives or strings or objects.

Wikipedia states

"first-class" is a computer science term that describes programming language entities that have no restriction on their use (thus first-class functions can appear anywhere in the program that other first-class entities like numbers can, including as arguments to other functions and as their return values).

This has some really cool implications

  1. A function can be assigned to a variable - Just like a primitive or string or object can be assigned to a variable
var head = function(array) {
	return array[0];
}

var first = head;

console.log(first([10, 20, 30, 40])); // 10

first and head are referencing the same function

  1. A function can be passed as a method argument - Just like a primitive or string or object can be passed as a method argument. Some of the best examples of this are
jQuery
$("input").onchange(function(value) {
	// do stuff with value
    console.log(value);
});

that can also be written as

var doStuff = function(value) {
	// do stuff with value
    console.log(value);
};

$("input").onchange(doStuff);
nodejs
var fs = require('fs');

fs.readFile("/path/to/file", function(err, data) {
	if (err) throw err;
	console.log(data);
})
  1. A function can return another function - Just like a primitive or string or object can be returned from a function
var dice = function (faces) {
	return function() {
    	return Math.floor(Math.random() * faces);
    }
}

var roll = dice(6);

roll(); // returns a random number between 0 and 5 (both inclusive)
roll(); // returns a different random number between 0 and 5 (both inclusive)

Side effects

In functional programming, a function should not have side effects. Let me get this out of the way, writing code with side effects is part of how I (and probably most programmers) have written code all along.

We learn that Side effects are bad, and functional programming, Wikipedia states

In computer science, a function or expression is said to have a side effect if, in addition to returning a value, it also modifies some state or has an observable interaction with calling functions or the outside world. For example, a particular function might modify a global variable or static variable, modify one of its arguments, raise an exception, write data to a display or file, read data, or call other side-effecting functions. In the presence of side effects, a program's behavior may depend on history; that is, the order of evaluation matters. Understanding and debugging a function with side effects requires knowledge about the context and its possible histories.

Some more examples of side effects

  • Insert/update/read from a database
  • read/write a file
  • mutations (read as i++ or i = i + j or str = str.concat(" world")
  • logging
  • read/update state to some centralized cache

It is not forbidden to have side effects, they should be minimal.

Pure functions

A function is pure if it returns the same output given the same input without any side effects.

some examples

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

This ^ is a pure function, given the same input a, b the same output is returned without any side effects

function insert(user) {
	if (DB.exists(user.id)) {
    	throw Error("users exists");
    }
    var id = DB.insert(user);
    user.id = id;
    return user;
}

There are multiple side effects going on ^ here.

  1. user is mutated with user.id = id so the input is changed
  2. Depending on the state of the DB the error "user exists" might be thrown
  3. in addition to returning the user the DB is also updated.

Testing this function is quite complex as the state of DB has to be set up and verified after the execution. In cases of multiple instances of this code running, it is impossible to assert the after state of DB

so this ^ function is certainly not pure.

Converting that to pure would be

function insert(DB, user) {
	return function() {
        throwIfUserExists(DB, user);
        var savedUser = saveUser(DB, user);
        return savedUser;
    }
}

given the same DB and user as input the output is always the same. It is a function.

Pure functions are easy to test, using different mock DB to test different cases is quite easy.

Now one might argue this means a long number of arguments, and we are going away from encapsulation, why would a program calling insert need to know about DB. Enter Currying

Currying

Before we talk about Currying you need to understand what a closure is

var addWith = function(a) {
	return function(b) {
    	return a + b;
    }
}

var add5To = addWith(5);

add5To(5); // 10
add5To(10); // 15
add5To(0); // 5

var add10To = addWith(10);

add10To(add5To(5)); // 20

the function returned by addWith takes an argument b but also has access to a which is an argument of its enclosing function. This function which has access to variables and arguments of its enclosing function is a closure

Let us start currying with an example

function add(a, b, c) {
	return a + b + c;
}
add(1, 2, 3); // 6
add(1, 2); // NaN - this is because c is undefined and when it participates in an arrithmetic operation it results in Not a Number

var curriedAdd = _.curry(add);

curriedAdd(1, 2, 3); // 6
var add3 = curriedAdd(1, 2); // returns a function
add3(3); // 6
add3(1); // 4

var add2MoreNumbersWith10 = curriedAdd(10); // returns a function
add2MoreNumbersWith10(1, 2); // 13
add2MoreNumbersWith10(1)(2); // 13

Currying a function with n arguments will return a new function which can take less than n arguments and return a function which will be another curried function that can take the remaining arguments.

Another example

function store(DB, user) {
	var newUser = DB.store(user);
    return newUser;
}

var curriedStore = _.curry(store);
var storeUser = curriedStore(DB);

storeUser(user);

You can partially apply some arguments to a curried function and use the returned function like it accepts only the remaining arguments.

lodash has a curry implementation

A means of achieving partial application is using the bind operator in javascript

function store(DB, user) {
	var newUser = DB.store(user);
    return newUser;
}

var storeUser = store.bind(null, DB);

storeUser(user);

Higher order functions

A function which either accepts other function(s) as argument(s) or returns a function is a higher order function.

We have seen these in the examples above, and having a function as a first class citizen is key to making this possible. This is key to functional programming, it starts to shine when you have many granular functions and then composing them together to make higher order function.

Pointfree

Functor

A Functor is a function which unwraps the input and calls a function with that, then wraps the output of the function and returns it

var add3 = function(a) {
	return a + 3;
}

add3(10); // 13
// add3 is a function

add3([1, 2, 3]); // "1,2,33" function add3 is not capable of handling both arrays and single items

[1, 2, 3].map(add3); // [4, 5, 6]

// map is a functor

Dont think imperative, think functionnal

Once you have choosen a language which provides these functional constructs, you got to write code Let us see a few cases of how you can tell apart the two different styles

for ... loop - bleahhhh

Say you want to find, given an array of student objects, find the average height of all the students who are in grade 3

// imperative
var students = [...]; // array of student objects
var sum = 0, numberOfGrade3Students = 0;

for (var i = 0; i < students.length; i++) {
	var student = students[i];
    if (student.grade === 3) {
    	sum += student.height;
        numberOfGrade3Students++;
    }
}

var averageHeight = sum / numberOfGrade3Students;

There are different ways to achieve this functional style, I will explain my favourite here

// functional
var students = [...]; // array of student objects

var heightOfStudentsInGrade3 = students
	.filter(function(each) {return each.grade === 3;});
    .map(function(each) {return each.height;}); // returns an array of height

var averageHeight = heightOfStudentsInGrade3
    .reduce(function(h1, h2) {return h1 + h2}) / heightOfStudentsInGrade3.length;

recursion is another way to handle this without using for loop, but I am not going to there

So for starters DO NOT USE FOR LOOP, that is not functional.

Immutable data

Remember functional programs do not have side effects, mutating an input or any data element is a side effect. treat all variables as constants and do not mutate them.

objects too, do not mutate them. so instead of

var student = ...;
student.grade = 3;
student.GPA = 4;

use ES6 Object assign or a polyfill

var student = ...;

var modifiedStudent = Object.assign({}, student, {grade: 3, GPA, 4});

or use ES7 spread operator

var student = ...;

var modifiedStudent = { ...student, {grade: 3, GPA, 4} };

All functions take and return

All functions accept one or more input arguments and return a value, either a function or a value

when to not use functional style

When working with api outside your control there is not much you can do, a good example I came across recently is the canvas api, for cases like that you will have to take exception to the functional style and go back to imperative

Conclusion

A program written following the above rules is considered functional, it is hard to not mutate data in-place and even harder to move away from for loop. Just hang in there. The results of that transition are simply beautiful, Pure functions are just a breeze to test.

References & other reading material

@tarungulati1988
Copy link

the function returned by addWith takes an argument b but also has access to a --> should be the function returned by addWith takes an argument a but also has access to b

@adamgajzlerowicz
Copy link

Thanks very much for this gist!
It is really helpful.

I believe that one of Maybe.of("John") should be Maybe.of(null)

Also - I am waiting for your description of Monad and Monoid!

@eedwards-sk
Copy link

eedwards-sk commented Mar 11, 2018

function insert(DB, user) {
	return function() {
        throwIfUserExists(DB, user);
        var savedUser = saveUser(DB, user);
        return savedUser;
    }
}

since when is throwing exceptions functional? or saving data?

So for starters DO NOT USE FOR LOOP, that is not functional.

why is a for loop not functional? Using a map or lambda just for the sake of using them does not magically make your code functional

@sp-niemand
Copy link

@eedwards-sk I guess, the for loop is not functional because you have to mutate the index to perform iteration.

@bagbyte
Copy link

bagbyte commented Mar 24, 2020

In your example, function insert(DB, user) is pure because it always returns the same value (another function), in this example:

function insert(DB, user) {
  return DB.store(user);
}

const storeUser = curry(insert)(DB);

Is storeUser pure?
Is it ok if some functions in the application are not pure?

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