Liked the taste of declarative programming? Want to generalize it and write arbitrary code in a declarative fashion, not just HTML pages, database queries or overpriced coffee?
Let’s see what we can learn from all the important concepts employed by declarative programming and the evolution of imperative styles.
- Dependence on global state is bad.
- We should avoid affecting the global state as much as possible.
- If we don’t have any mutable state, the order of our code won’t matter.
So, how do we get from here to there?
- We’ll start with our friends functions from procedural programming. They represent isolated, well-defined and reusable units of work - that’s good.
- Since we want to limit the dependence on global state, we’ll only allow the function parameters to affect the behaviour of the code, not just any variables in the rest of the program.
- Since we want to limit the effects our code has on the outside world, we’ll only output the results as the function return value. Other than that, any side effects are prohibited.
- Instead of mutating variables we will produce new immutable values based on the old values.
The easiest way we can visualize such a function is as a pipe. It receives all its inputs on one side, does some work internally, then produces all the results on the other side.
Our coffee example would involve one coffee-producing pipe that receives information on what kind of coffee you want on one side and outputs a hot and ready cup on the other. A bit like an espresso machine, actually.
We will simulate this with a JavaScript function that takes the desired sugar and milk options as the input and produces a cup of coffee.
function makeEspresso(sugar, milk){
if(milk){
return {sugar: sugar, coffee : 1,
milk : 0.5, water : 0.5};
}
return {sugar: sugar, coffee : 1,
milk : 0, water : 1};
}
Let’s compare this function against our checklist:
- Does not depend on any variable outside its scope - the only things that affect its behavior are the parameters. Check!
- The only way the function affects the outside world is by returning a value. Check!
- While JavaScript objects are mutable by default, see that we never actually mutated the objects that represent the cup - we always created new objects. Check!
- Since we didn’t mutate any state, the order of our code does not matter as much. Instead of checking if milk is wanted, we could have checked the opposite. The following code is just as good:
function makeEspresso2(sugar, milk){
if(!milk){
return {sugar: sugar, coffee : 1,
milk : 0, water : 1};
}
return {sugar: sugar, coffee : 1,
milk : 0.5, water : 0.5};
}
One unsaid requirement for all this to work is unlimited supply of empty cups, sugar, coffee, water and milk. Also let’s not forget the electricity the machine needs to run. We will simply hand wave all those details and smugly declare that low level resource management is not a functional way of doing things.
Anyway, let’s make a few espressos:
makeEspresso(2, false);
//=> {sugar: 2, coffee : 1, milk : 0, water : 1}
makeEspresso(0, true);
//=> {sugar: 0, coffee: 1, milk: 0.5, water: 0.5}
makeEspresso(1, true);
//=> {sugar: 1, coffee: 1, milk: 0.5, water: 0.5}
//just for fun try the other one too
makeEspresso2(1, true);
//=> {sugar: 1, coffee: 1, milk: 0.5, water: 0.5}
Notice how we always get a brand new cup? Instead of reusing it, we have to dispose of it. That’s not very green! Seriously, it’s not a coincidence that garbage collection was first used in a functional programming environment.
We lost a lot of low level control over resources, but what we gained is a piece of declarative code that we can actually run. Just look at it, this snippet:
makeEspresso(1, true);
It’s nothing other than our declarative pseudocode thinly disguised as JavaScript:
One espresso with 1 teaspoon of sugar and milk, please!
You declare what you want, not how you get it.
So, we ventured from an imperative program to a declarative one and thanks to functional programming we didn’t even need to build a coffee shop to do so. We managed all that because we adhered to the principles enumerated at the start of this chapter. We will explore those principles and functional programming in general throughout the rest of the book.
So, stay with us - the good stuff has just started!