Functional programming languages such as Haskell, Scheme and Common Lisp have existed for years and provide all the tools needed to program in a manner that aims to eliminate side effects associated with procedural and object-orientated languages. This has the benefit of producing less code that is easier to maintain, and usually uses things like lazy evaluation to provide infinite lists and lambda anonymous functions unavailable in other languages.
However, compared to learning popular languages such as Java, C++ and Python functional languages are vastly different and much more difficult for beginners to grasp. Code written in a functional style is usually quite obfuscated to an outsider who has no notion of what functions like map, reduce and zip accomplish. In enterprise applications, where clarity of code is more often than not considered more important than how elegant the solution is, using a functional language is usually out of the question.
Despite this, I believe that doing things in a functional manner is not a bad thing at all, but more importantly, we can all learn something from the functional style. Here are some of the key properties of functional programming that if used correctly can make you a much better programmer regardless of what language you code in.
#1: Eliminate Side Effects
Side effects are any data that is determined at runtime and thus we cannot predict what it will do. The most common side effects are application state and I/O. Obviously both of these thing are used widely in nearly every single application used today and we can’t just go removing them. However, we should make a clear distinction in our functions about which ones produce side effects and which ones are considered pure.
Pure functions are those that do not suffer any side effects. Most of the time in a language like C or Java, we will not be able achieve true purity and somewhere along the chain of execution a side effect will be involved and thus all functions from there on will be transitively impure. By separating these functions into new ones, we will achieve easier error handling and data validation as a direct result.
#2: Pipelining functions
Pipelining function calls is probably something you’ve always done but not known the term for it. Consider the following Python code:
print inputPlus4(divideBy2((6+4) + (10))) >>> 14
By nesting our function calls, the return value of the first call becomes the input parameter for the second and so on. This may seem very simple but in reality it is very powerful. Wherever possible, all our functions should have a return type, and we should only use void when we absolutely must. Once a method is made void, we lose all the power that pipelining provides. Worse, it’s almost certain that by having lots of void functions in your code, lines of code will increase and more code means more maintenance. Pipelining used correctly also means less variables, and that’s another bonus for maintainability.
#3: Consider recursion
Now I’m not going to be purist here and say that recursion should be the only method of iteration you use in your code, but if it makes sense to use recursion then do it, even if it takes a little more time and ingenuity to do so. A classic practical example of recursion is calculating Fibonacci numbers. If you Google “Calculating Fibonacci numbers in X”, where X is you programming language of choice, you’ll find plenty of examples. The main advantage is that a recursive function can generally be proven mathematically correct more easily than if the function used a for or while loop.
I could give plenty more examples, such as the advantages of using lambda anonymous functions in Python, but I want to keep things language independent for now. I may follow up this post in the future with some examples of pure functional programming in Python, and even C# for the .NET enthusiasts out there.
To finish off, I’d like to reiterate over the main points of this post:
- Keep code that deals with side effects separate from that which does not.
- Aim to achieve function purity.
- Functions should take input and return a value whenever possible. Void functions should be kept to the absolute minimum required by the solution.
- Use recursion where it makes sense.