An Intro to Practical Functional Programming in Javascript
There are the often simple explanations, Pure functions, no side effects, etc. All these sounds like huge limitations to most programmers who are used to using for loops etc. I’m going to talk about it in a different way.
Functional programming is different way to program. It’s not always a better, but it can often be more resilient to bugs. Also, it doesn’t just have limitations, but also it’s own set of features or powers. Let’s look into those:
First Class Functions #
If you have a background in C/C++, this concept may seem a little foreign to you. (C++ has add function pointers now) But this perhaps one of the most powerful features that enables functional programming. First class functions means nothing other than the fact that functions are just another type of value that can be passed around, stored in variables, accepted as arguments in other functions and returned. Without first-class functions, functional programming would be impossible.
Some OO languages like Java make something similar possible. In java, it’s possible to pass objects around, and object can have methods, which are just functions attached to state. Now, I’m not going to say that that is not useful, (though many functional programming converts will agree that it’s a much worse way of doing things), first class functions just helps remove all that ceremony and makes thing more direct.
Another aspect of first class function, and this is actually pretty common in low-level languages like C is recursion. Specifically, tail call recursion.
Now, getting back to no side-effects. Functional Programming in a strict sense doesn’t let you change any value. Imagine you have no variables, just constants. I know it can sound very annoying, and I agree, being academic about functional programming can be annoying. But there is often a point to all this. And, even if we don’t adopt functional programming fully, we can learn many lessons.
Imagine a for-loop, it essentially works by keeping a variable and updating it’s value on every iteration. Now that breaks the no-variables rule. So how would you do a loop in a functional way? Well, with recursion, it’s actually pretty simple. Take this example, written in javascript.
for (var i = 0; i < 100; i++){
console.log('something')
}
// can be written as
function forLoop(currentValue, test, change, action){
if(test(currentValue){
action(currentValue)
forLoop(change(currentValue), test, change, action)
}
}
// which can be used in a very similar way
forLoop(0, i => i<100, i => i+1,
i => console.log(i)
)
Once you have your loop function written out, it’s actually pretty easy to use it in almost the same way. (Also, I give myself bonus points for using ES6 fat-arrow functions to be able to use the functional for-loop in almost the same layout!)
These kind of example are usually the reason people don’t see the power of functional programming. You might be saying to yourself, “yeah, but it’s still just a for loop, how does this help?”
You see, when you start thinking of loops as recursive functions, you suddenly get a lot more power and can do things that would be impossible with just for loops.
Imagine a simple example of looping over an array. We’ve all done that many times and it’s trivial with either approach. Now imagine, you have an array with nested arrays:
[1,2,[3,4], 5]
You think, simple just two nested for loops. But let’s say the arrays can be nested more than two levels deep. How many loops will you nest? Even if you write 20 nested for loops, there may always be the chance of an array nested 21 levels deep.
This is why even low-level languages provide an easier way to do this with recursion. Doing the same thing without recursion is possible, but in this case is much harder than recursions. Thinking in a functional way often is harder in the simple cases, but often make life simpler in the more complicated cases.
Let’s look at another functional concept.
Immutable Data Structures #
In functional patterns, you use a special data structures based on structural sharing. This is just an implementation detail so you don’t have to think too much about it. Just think about this: Every time you added an element to an array, you got a whole new array. Your old array is not destroyed but you still get the new result you want. I want to make a important point here:
Immutable Data doesn’t mean your variables can’t change. Even seen code like this:
var a = a + b
You can still do that with immutable data.
So, if you only had immutable data, what are you really losing? Turns out not, much. Let’s look at a few examples:
- A function can’t take data, you pass to it and change it in a way that it also changes for you:
var changeObj = function(obj){
obj.a = 2
}
var myObj = {a: 1}
changeObj(myObj)
console.log(myObj)
Take that for example. Can you imagine how difficult things would get if this could happen for simple integers? Say you pass x
into a function and that function calls x++
and the value changes on your end? That would be ridiculous. But that is exactly how we’ve been dealing with complex data.
If you do want to change the value of your data, you want to control that.
var myObj = changeObj(myObj);
That is the way you would deal with things with immutable data.
Seems pretty obvious and simple. So you might be wondering, why do we deal with mutations in the first place? Turns out copying large amounts of data can my expensive, and to avoid doing that we just decided to share access to the same data in many parts of a program.
So is Immutable data really expensive? Here, let’s go back to that concept called Structural Sharing. Turns out that Immutable Data structures are built on complicated tree structures called tries. These trees are built with a bunch of pointers, and when you make a change, usually you only need to copy a very small amount of data. The common parts of the new and the old data structure are just ‘shared’.
Currying #
This is a concept that seems to be much more complicated than it really is. In fact, Currying as a features serves little purpose than saving you typing. And in the simplest sense, you can think of Currying as inheritance for functions.
Let me throw a very simple example out there. Say you have an application where you often have to filter array for undefined values. You might do something like this:
arr = arr.filter(function(a){ return a !== undefined })
Now if you were doing this a lot, you might just take that function and store it.
var notUndefined = function(a){ return a !== undefined };
arr = arr.filter(notUndefined)
So, now you don’t have to write the same function over and over again, every time you need to filter out undefined values. Obviously, this is a stupid example, but you can probably imagine a more complicated predicate function.
Now, if you still wanted to reduce the amount of code you had to write, you may probably factor out the filter call itself.
var filterUndefined = function(arr){
return arr.filter(notUndefined)
}
arr = filterUndefined(arr);
Again, you can probably see the value for a more complicated case..
And now, you suddenly need to filter an array for null values! You don’t want to write essentially the same code all over again. This is where currying helps you. Currying lets you write functions for the general case, which take more arguments to make them work for specific cases.
var filterBy(func, arr){
return arr.filter(func)
}
var notEqual(val1, val2){
return function(val2){
return val1 !== val2
}
}
arr = filterBy(notEqual(undefined), arr);
arr = filterBy(notEqual(null), arr);
Now that achieves maximum code reuse but you suddenly have to write a lot more arguments every time. If you wrote you code like this, in a real app you would be writing functions that returned functions that returned functions all the time. And calling functions would be a mess.
Currying just takes that problem away.
This is what currying gives you:
var notEqual(val1, val2){
return val1 !== val2
}.autoCurry();
// is equivalent to:
var notEqual(val1, val2){
return function(val2){
return val1 !== val2
}
}
But it is also equal to:
var notEqual(val1, val2){
return val1 !== val2
}
So basically, think of it as a lazy function. It will keep returning functions till you feed it enough arguments.
NOTE: Curried functions can get messy when you want functions that can take a variable number of arguments.
So now you can define some methods once:
var notUndefined = notEqual(undefined)
var notNull = notEqual(null)
var filterUndefined = filterBy(notUndefined)
var filterNull = filterBy(noNull)
And now you can easily use these high level methods all over your code base:
arr = filterUndefined(arr)
A note about curried functions. Curried functions only work, when the data you pass in is the last argument. Notice how the filter function takes the predicate function before the array.
Where to go from here #
Many people treat functional programming as completely exclusive from OOP. Well that’s just being super academic. It’s totally possible to mix functional programming concepts with Object oriented programming. In fact a majority of javascript programmers do just that every time they use Array.map in an otherwise object-oriented code base.
Now is the time to embrace some more functional concepts (and honestly, technology like Immutable data) as it will likely lead to a small and more reliable codebase.
Javascript is not Haskell. It gives you a lot of the power of functional programming without the shackles. So you can use functional concepts when you want to and drop back to simple for-loops every now and then when you need to.