Functional Programming Saves Lives

April 13, 2020

What is functional programming (FP)

There are a lot of definitions of functional programming, some more complex than others. My working definition of FP is:

Building software by bringing together pure functions that avoid mutation and side-effects. A pure function, when given the same input, will always return the same output, as it is stateless.

My journey with functional programming is fairly recent. I recently read 'The Unicorn Project' which talks a lot about refactoring old code and making it more readable and testable using FP practices. 

At FindMyPast we tend to write the majority of our projects using JavaScript and when we write JavaScript we tend to write it in a functional style, although not strictly enforced. I have leaned towards writing more functional JavaScript, it was never a conscious decision I made I just had a nagging feeling that code without any 'let' declarations or for loops were safer. FindMyPast also has legacy code in Elixr, a purely functional programming language. 

But how does it save lives?

Okay, maybe I exaggerated, you're not going to be able to write a functional program to create a fountain of youth. However, peoples lives do depend on software in many ways, some less critical than others but all are important. 

As software engineers, we have a responsibility to make the software that people rely upon as robust as possible and prove to them that they can rely on it safely. I believe that one way to get to more robust software, without slowing down on features, is by adopting functional programming ideas into our workflows.

Side effects suck

One of the most compelling benefits to functional programming is its aim to eliminate as many side effects as possible and isolate necessary side effects away from all business logic. Side effects cause all sorts of problems in production applications and they make the problems they cause harder to debug.

Object orientated programming relies heavily on side effects. Calling a method on an object that returns nothing is valid in an OOP world. OOP frames this as a benefit, you don't need to worry about what is being done, it is all been encapsulated nicely by the class you are calling.

Here is a situation of how that can hurt you and cause bugs. Say you have been tasked to create a machine that accepts money and adds it to your bank balance. You think this will be easy, we already have a cheque machine so I can just reuse the class they built:


A few weeks later I get an email saying asking why we there so many requests sent to the fraud checker system, it should only be getting asked about deposits made by cheque. That's what happens when you leave the side effects to be encapsulated by a class. 

Now, of course, you could pass a second parameter into deposit saying what kind of deposit it is, then wrap the notifyFraudCheckerSystem call in an if statement. That would work in this case but that approach gets messy quickly and then you are soon left with a big ball of mud and it can take days to make sure that any changes you make don't cause unintended side-effects.

That is why side effects suck and you should avoid them when you can

Functional Code is easy to parallelise

Data races are annoying and painful to debug. Sometimes the application works fine, but randomly stuff breaks, you can't consistently reproduce the problem.

The bugs come in when multiple threads try to update the same global state at the same time. This problem can be solved by locking global state when you are using it, but then you have to remember to unlock it when you are done. 

Side-effects and global state make horizontally scaling harder as they create a dependency across nodes, that needs to have some sort of locking system in place. As FP reduces both of these it can become much easier to scale horizontally.

Its probably worth noting, that if you have a poorly scalable system don't blindly rewrite it functionally as there is a high chance your bottom neck could be the DB which functional programming won't fix.

Testable

The idea of a function always returning the same result given the same input makes super easy to test. When changing an existing function the tests around it should only start failing if you change what the function returns. When writing functional code creating tests that adhere to this rule becomes much easier as you can safely treat the function as a black box because your pure function won't be doing any side affects you need to test for.

Making it practical (in Javascript)

Functional programming is often associated with languages like Haskal and Closure as they are strictly functional, you couldn't write imperative code even if you wanted to. However, these examples are in JavaScript as that is what I primarily write.

In this example, my webserver is taking a request, requesting something from another service then returning it, it also transforms data on the way in and out in complex ways.

Heres how it might look in a non-functional codebase:

This implementation is going to be hard to test as all of the logic are contained in a function that also has a side-effect, so something will have to be mocked. 

Here is how it would look if written in a functional codebase:

This solution puts all of the business logic into pure functions that can be nicely extended and tested. If you chose to do integration tests you are still going to have to do some mocking so you can't completely avoid it, but you can have a lot more confidence in the tests around your business logic.

Don't you just end up with a bunch of copies?

When I started to think about functional programming I had a nagging thought at the back of my mind about memory efficiency. If nothing can ever be mutated, what happens if we only want to change one element in a massive array? Do we have to create a whole new array and copy most of the original one and change one value? This was how I assumed it would have to work, which doesn't seem like it would scale at all.

'Persistent Data Structures' solves this problem by splitting your large data structure up into smaller chunks. That means you can do your safe modifications to your large array by copying only the smallest chunk. It can get really complex and has some downsides but it solves the copying problem for most use cases.

But we need state to get stuff done?

Yup, we definitely need state and side-effects to give our users value.

Customers don't think of side effects as side effects but rather as the desired effect - Russ Olsen 

Functional programming removes as much state as possible but you are still going to need a database to store user details or to call another API to get some side-effects done. FP encourages us to isolate the 'side-effecty' code away from all other logic.

Functional programming isn't magic, and you can still write really awful code in functional languages. However, refactoring the code to make it less awful is a lot easier and you can have high confidence in your awful code with simple and comprehensive test cases of all the messy logic it does.

I really enjoyed writing this post and learned a lot while doing it. Here are some of the resources I learned from:

Tags: