In this series Eonics consultant Rutger van Velzen leads us down the rabbit hole of Functional Programming. In this second part he introduces pure functions & lazy evaluations. Links to the other parts can be found at the bottom of this article.
Functional programming offers powerful building blocks which (depending on the language) drastically reduce or nearly eliminate state and or side effects.
Let’s break this down, there is a lot of information in that one-liner.
A side effect is when a method or function changes the state of something outside its own scope. A variable has a state when the value in it can change at a point in time.
Of course this highly fluctuates based on the language you are using, some languages are pure functional languages and are aiming to be as close to lambda calculus as possible and if the language allows an assignment at all, you will be discouraged to use it. Other languages are impure, they are functional without a doubt and also support other paradigms; others are even called hybrid languages, a language where functional and object oriented meet.
All those languages are functional because they share the same building blocks, and those building blocks are… Functions!
One of those Building blocks is called: lazy evaluation, which we will be talking about today! But before we do that, we take a peek at Pure functions and function tables.
A function is pure when it has no side effects, and does not use state. A state is everything that can change while the application is running, a class variable, an increment counter, date or time values; to name a few. Side effects are every action that does not contribute to the functions output, calling a logger, writing to the database, collecting user input, things like that.
A pure function can call other pure functions, but when it uses an impure function somewhere down the chain, it stops being pure. This way the function is solely responsible for the data it processes. A pure function is predictable in a way.
A great real-life representation of a pure function is an old fashioned coffee grinder. It takes beans as input, and has coffee powder as its output. There are no side effects, nothing is logged or triggered and it does not use other ingredients from elsewhere.
Beans go in, powder comes out; nice and simple.
Before the first mechanical calculators and slide rulers were even invented mathematicians made books containing multiplications of large values paired with their results. So instead of calculation 675 x 978 the mathematician opened a large book and looked it up; and knows for sure the answer is 675 x 978 = 660150. We call those books tables, yes the same multiplication tables you had to learn as a kid at school!
With a pure function we can make a table as well, one column for the input values, and another for the output values. If we look back at our coffee grinder, it will be straightforward how a table may look like. Every pound of beans translates into a pound of powder, it has the same mass, only the size has changed.
The fastest way to calculate something can be achieved by not calculating anything at all! The tables we just discussed made that very clear. When I ask you what five times five is, you don’t have to count it, you just know.
The hard part is when and what kind of table to use. Simple multiplication tables are slower than just calculating it. Storing a large table on the harddrive is rarely worth it, though it has its use cases, in computer chess for example. A common approach is to predict or monitor the load on a function, and if the function is often called with the same values. Then it is a great moment to make it lazy!
There are libraries that can do this for you, but you can also make it yourself. When you have a pure function, you look up the passed parameter(s) in a hashmap. Does the hashmap contain the parameter(s), you return its value. If the hashmap does not contain the parameter(s), you run the function and store the given parameter(s) as key and the function’s return-value as value in the hashmap, so you have those ready for the next time.
That was all for today, in part 3 we will delve deeper into functions and talk about deferred execution.
Functional programming: the complete series
New articles in this series will be added to this list as soon as they are published.
- Part 1: historical context
- Part 2: pure functions & lazy evaluation
- Part 3: first-class functions, higher-order function and deferred execution
- Part 4: coming soon!
For feedback, questions or comments feel free to drop Rutger an e-mail.