Understanding Functional Programming: An overview of the basics
For quite some time I’ve been interested in learning more about functional programming. But the question I always ask myself before diving into any to topic is:
What is INSERT_TOPIC_HERE and why should I care?
The trend towards functional programming, specially in the area of big data, is steadily growing. So there must be something to it because, if not, the buzz would not be meaningful enough to gather the attention it has.
So… what is functional programming? And why should I care?
That is what I’ve decided to find out, and this post hopefully will bring some light over what it is and why should anyone bother with it.
What is Functional Programming ?
The definition from the wikipedia page on functional programming [1] explains very well the core principals of what functional programming is:
“In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions.
It is a declarative programming paradigm in which function definitions are trees of expressions that map values to other values, rather than a sequence of imperative statements which update the running state of the program.”
The keyword here is composition. In functional programming, functions are treated as first-class citizens (i.e., they are treated just like any other data type), where small functions are combined in a modular manner into more complex functions. This leads to programms being written in a very declarative and composable fashion.
The power of composition is what gives functional programming one of its biggest strengths. Following this philosophy of composition allows for connectable, reusable parts to build bigger things from smaller things, emphasizing modularity as you go along in building components.
But the second core principal of functional programming involves around mutating state (and, consequently, side effects). In FP, state is handled explicitly in functions: their output depends only on their input and there is not external environment that changes this assumption. This is refered to as pure functions, meaning that the same function will return the same output for the same input, regardless of the order in what it is executed.
The goal is to avoid implicitly changing the state of the world by handling data as if it was immutable. Once created it is not changed, and the only way to change its contents is to create a new data structure with the changes applied to the new structure. Also, in the same way that mutating data implicitly is avoided, so is sharing state between threads or processes.
But also keep in mind that not all state is bad. Sometimes, mutable state is necessary to solve certain problems. The goal of functional programming is to make the state visible and explicit in order to eliminate any side effects.
The whole idea of FP is to introduce consistency and predictability to our system, having explicit transformations of data in plain sight of the developer, reducing the number of unexpected behaviours (surprises) as much as possible.
So… why should I care about functional programming ?
The benefits of applying the principles of composition and immutable state to complex systems brings many advantages when using functional programming:
-
Cleaner code: data is not modified once defined, so understanding the flow of a chain of computations gets simpler for the developer to keep track on his mind.
-
Referential transparency: Expressions can be replaced by their values. If we call a function with the same parameters, we are certain that the output will be the same.
-
Memoization: Cache results from previous function calls.
-
Idempotency: You get always the same results for the same inputs.
-
Modularization: Complex problems easier to solve by being modelled using smaller, well defined functions (think of lego blocks).
-
Ease of debugging: Functions are isolated pieces of logic, depending only on their input, so they are easier to test and debug.
-
Parallelization: Function calls are independent, so they are easily parallelizable in different processes/computers.
-
Easier concurrency: With no shared data, concurrency gets much simpler:
- No semaphores;
- No monitors;
- No locks;
- No dead-locks;
- No race-conditions.
So, to answer the question, we should care about functional programming because:
- It’s generally more concise
- It’s generally more predictable
- It’s easier to test and reason
Note that these are key aspects revolving working with data. In many cases, it involves working with large sets of data points with relatively complex chains of transformations that, depending on the scale of the operation, are processed on multiple machines that make testing and debugging especially hard when issues arise. Thus, having a process to reduce the overall complexity of a system is a huge deal.
Composition in functional programming
Being one of the core principles of functional programming, there are several ways to compose things. Commonly used techniques to accomplish function composition are:
- Piping (usually with something like “|>”)
- Currying/partial application
- Composition using “bind” (monads)
- Kleisli composition
Often these techniques are used together to compose functions in order to create chains of computations. Each technique has its purpose, but when used together they offer a solid way to write functional code in a simple way.
Piping
Piping is used to perform a sequence of operations on some value (just like piping in Unix). The input to each function is the output of the previous function. This requires that each function in the pipeline should receive a single argument in order for this to be feasible.
However, functions can have more than one input parameter. To solve this, we can use currying to transform functions in order to enable them to receive a single input instead (see currying).
Currying
Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each takes a single argument [2].
For example, currying a function f that takes three arguments generates three functions:
x = f(a,b,c) becomes:
h = g(a)
i = h(b)
x = i(c)
or called in sequence:
x = g(a)(b)(c)
In simple words, it “spaghettifies” the function’s arguments, converting a N parameter function into N functions with a single parameter.
Monads
Monads are a way of chaining things together with different shapes between them. They turn them into the right shape so they can be composed.
Monads are data structures that allow you to do some clever function composition in a simple way. Additionally, they are commonly used for [3]:
Order of evaluation: Since languages that provide monads by default tend to be pure functional and declarative, the exact order of evaluation is not known. One thing that monads are used for is to enforce a particular order of evaluation in places where this is important.
Side effects and IO: Pure functional languages ban side effects, so handling things like mutable state, errors and IO don’t work the way most of us are used to. Monads allows to do side effect-like operations in a pure functional context.
When reading about monads, you will hear some technical jargon like Monoids, Functors and Applicatives which may lead to some confusion because these are not familiar terms. These are, however, not that complicated if you replace them with some more common terms like:
- Monad -> “chainable”
- Monoid -> “Aggregatable”
- Functor -> “Mappable”
These don’t sound as complicated compared to their more technical terms (which were given by mathematicians), but these are what they essentially are used for.
Kleisli composition
Say you have two functions g and f and we know how to compose them:
f1: A -> B
f2: B -> C
composing A -> C would be:
f2(f1(x))
But if we have two functions that return a monad we cannot directly compose them. For that, we use kleisli composition.
Kleisli composition is another jargon which basically means composing Monads together. I won’t go into much details here, but if you are looking for a more detailed explanation on kleisli composition, please see [4].
The Functional Toolbox
The following list is a compilation of methods related to functional programming that are commonly used in this programming style for a variety of situations:
- composition -> compose
- Combination/aggregation -> fold
- Iteration -> combine & reduce
- Working with effects
- Mixing effects and non-effects -> map & return
- Chaining effects in series -> bind/flatmap
- Working with effects in parallel -> apply & zip
- Pulling effects out of a list -> sequence & traverse
Additionally, if we add the FP jargon we’ve encountered in the Monad section to this list we get:
- Conbination/aggregation : Monoid
- Working with effects
- Mixing effects and non-effects : Functor
- Chaining effects in series : Monad
- Working with effects in parallel : Applicative
- Pulling effects out of a list
Don’t sweat if you don’t know all of these at this moment, since different programming languages will have slightly different names for them. The goal of this list is to help you be aware of such terms when encountering them in your favorite language of choice.
Conclusion
This post revolved around the fundamentals of functional programming (composition and immutable state) and why you should use or consider using them in your coding style.
Writting purely functional code can be quite straight-forward and fun at the same time. The concepts are quite simple, but effective nonetheless. There is much more about functional programming than what was covered here, but don’t stress too much about it if this is any kind of a concern to you. Knowing the basics will take quite far into functional programming if you wish to devel into it.
If you want to continue to dive deeper in functional programming and know more, I suggest starting by watching some videos from Scott Wlaschin [5] about functional programming. I’ve found them very useful when researching about this topic of functional programming.
Hope you found this useful.
Cheers
Sources and Useful Links
[1] https://en.wikipedia.org/wiki/Functional_programming
[2] https://en.wikipedia.org/wiki/Currying
[3] https://jasondelaat.github.io/pymonad_docs/explanations/whats-a-monad.html
[4] https://functionalprogramming.medium.com/monads-kleisli-composition-5b42fe2f92f2
[5] YouTube: The Power of Composition - Scott Wlaschin - NDC Oslo 2020
https://medium.com/@lettier/your-easy-guide-to-monads-applicatives-functors-862048d61610