Functions are the basic unit of reusability
Functional programming is programming with functions. Shocking, no? Who knew?
Functions are the basic unit of reusability. If we have a few lines of code, and they do something that we might want to do again somewhere else in our code, then we can wrap them in a function. Then we can use that function throughout our code.
We don't have to write the algorithm over and over again, which might mean making mistakes and getting different results. It's wrapped up in the function, and we just call the function to run the code and get a result back.
We probably want the function to behave slightly differently in different contexts, so we can pass it arguments. These are the parameters of the function: the context in which it operates. The function takes one or more arguments and returns a single result.
If you think about this, we can consider the input to the function to be the current state of our application (part of it anyway), and the result of the function represents a new, transformed state.
So functions are the way we change state in our application. Essentially, a function is a black box that takes state (in the form of parameters) and returns new state (in the form of a single result):
For example, suppose we wanted to be able to calculate the area of a circle given its diameter. We know that the area of a circle is the radius squared times π (the Greek letter PI), which is 3.141592653589793 in JavaScript and is available in the Math
module as Math.PI
.
We can create a function called areaFromDiameter
that takes the diameter as a parameter and returns the area of the circle with that diameter:
1
2
3
4
5
6
7
function areaFromDiameter (diameter) {
const radius = diameter / 2
return Math.PI * radius * radius
}
areaFromDiameter(2) // Radius is 1, 1 squared is 1, area should equal PI
Now we can use that throughout our code. See? Functional programming is easy!
Given the same arguments, a function must return the same result
Put another way, what a function returns depends entirely on the arguments passed to the function. Avoid as much as possible writing functions that return a value independently of the inputs.
Here is a good example: the add
function. It takes two numbers and returns the sum of the numbers. If we call it with 5 and 7, then it always returns 12. It doesn't sometimes return 12, sometimes 11, sometimes 13. Right? Make all your functions work this way and your life as a programmer will get much, much easier.
1
2
3
4
5
6
7
function add (x, y) {
return x + y
}
console.log('Does 5 + 7 equal 12?', add(5, 7) === 12 ? 'Yes!' : 'No')
console.log('Is 5 + 7 still 12?', add(5, 7) === 12 ? 'Yes!!' : 'No')
console.log('What about now?', add(5, 7) === 12 ? 'Yes! Still 12! UNBELIEVABLE! Who knew?' : 'Uh, oh')
Functions take at least one parameter
You have probably noticed that we can create a function that has no parameters. How about this one?
1
2
3
4
5
6
7
8
9
function alwaysTrue () {
return true
}
// Let's try it!
console.log('Calling alwaysTrue', alwaysTrue())
console.log('Calling alwaysTrue again', alwaysTrue())
console.log('Calling alwaysTrue one more time', alwaysTrue())
console.log('Sigh...')
Kinda boring, huh? Remember that a function always returns the same result for the same arguments. We can't pass this function any arguments, so we can't change the output in any way. We have a name for this kind of function. We call it a constant.
Ha, ha. Get it? Because its output never changes.
Believe it or not, a parameterless function can be useful in rare circumstances in which we want a function to return the same thing every time it is called. There's an exception to every rule, no? But in daily use, remember that functions take at least one parameter.
Functions return exactly one result
You probably already know that you can't return more than one value from a function. But what we mean by “exactly one result” is that you should always return a value. A function that does not return a value is useless—because the only way a function can change anything is with its return value.
To be clear, that value might be the value undefined
. But if you are returning undefined
from your function, it should be because whatever is calling the function expects undefined
to be a possible result and will act on it accordingly. That's different from not returning anything—even though a function without a return statement will, by default, return undefined
.
To make this explicit to the next programmer—that you intended to return undefined
—return it explicitly: return undefined
. Here is an example:
1
2
3
4
5
6
7
8
9
10
function testN (n) {
if (n <= -1) { return false }
if (n >= 1) { return true}
return undefined
}
console.log('Testing -5:', testN(-5))
console.log('Testing 7:', testN(7))
console.log('Testing 0:', testN(0), 'Yeah, we expected that.')
There may be rare occasions when we want to return multiple values from a function. In this instance, we can wrap them up into a composite value and return that. Then we can “destructure” our values out of the result on the other end.
For example, we can use the JavaScript Array
as a kind of “tuple”. A tuple is an ordered list of elements, typically having different datatypes, such as a number, a string, and a boolean. In JavaScript, arrays aren't limited to a single datatype, so they can work as tuples.
Here we have a function that calculates the area and the cirumference of a circle given the radius and returns them in a tuple. We can then destructure them from the tuple:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Tuple = Array // Just for fun
function getAreaAndCircumferenceFromRadius (radius) {
const area = Math.PI * radius * radius
const circumference = Math.PI * radius * 2
return new Tuple(area, circumference)
}
const circleProperties = getAreaAndCircumferenceFromRadius(1)
// In an arry, ordering is important, but you can name your variables
// whatever you like: values are assigned by position
const [area, circumference] = circleProperties
console.log(`The area is ${area}`)
console.log(`The circumference is ${circumference}`)
In general, returning multiple values from a function is a code smell. Try to avoid it. If you must do it, at least delegate the work to smaller, single-purpose functions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const calculateArea = radius => Math.PI * radius * radius
const calculateCircumference = radius => Math.PI * radius * 2
function getAreaAndCircumferenceFromRadius (radius) {
// We could return an object instead
return {
circumference: calculateCircumference(radius),
area: calculateArea(radius)
}
}
// When destructuring an object, the order is unimportant
// but you must use the key for your variable name
const { area, circumference } = getAreaAndCircumferenceFromRadius(1)
console.log(`The area is ${area}`)
console.log(`The circumference is ${circumference}`)
Notice how the name of our function is kind of long? That's a hint that our function is probably doing too much. Refactor, if possible.
Functions can be passed as arguments to other functions
Here is where FP starts to come into its own. If we can pass a function as an argument to another function, then our functions become exponentially more powerful.
This is best explained with an example. The JavaScript Array
object has a sort
method. The default is to sort in ascending order. This uses a “comparator” function that takes two elements from the array and compares them.
If the elements are of equal priority, meaning their ordering doesn't matter, then the comparator function returns zero. If the first argument should come before the second, then the comparator returns a positive number; if it should come after the second argument (reversing the order), then it returns a negative number. So our default comparator for an ascending (a-z) sort looks like this:
(a, b) => a - b
But what if we want to sort in descending (z-a) order? Well, we could make our sort
method take a constant, such as ASC
or DESC
and switch up the comparator function so on DESC
it was (a, b) => b - a
, right? But our options are pretty limited here, no? For example, what if we wanted to sort a list of strings by the first uppercase letter in the string? Uh, oh.
So the vanilla JavaScript sort
method allows us to pass it our own comparator function. Items in the list (array) will be passed to the comparator two at a time, and the comparator should return a positive number (keep the ordering), 0 (doesn't matter), or a negative number (reverse the ordering). How it does that is up to us.
Here is our example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const asc = (a, b) => a - b
const desc = (a, b) => b - a // Reverse the ordering
// String.match returns an array, the first item of which is the string matched
// In this instance, that will be the first uppercase letter
// Then charCodeAt(0) extracts that letter from the string and converts it
// to its character code, between 65 (A) and 90 (Z)
const getFirstCapCharCode = s => s.match(/[A-Z]/)[0].charCodeAt(0)
const firstCapAsc = (a, b) => getFirstCapCharCode(a) - getFirstCapCharCode(b)
const firstCapDesc = (a, b) => getFirstCapCharCode(b) - getFirstCapCharCode(a)
console.log('Sort [3, 5, 1] ascending:', [3, 5, 1].sort()) // Defaults to ascending
console.log('Sort [3, 5, 1] ascending:', [3, 5, 1].sort(asc))
console.log('Sort [3, 5, 1] descending:', [3, 5, 1].sort(desc))
const strs = ['stRinG', 'nuMber', 'Boolean', 'arraY']
// The default string sort will sort uppercase before lowercase
// as it uses the character codes (A-Z is 65-90, a-z is 97-122)
console.log('Default string sort:', strs.sort())
console.log('Sort strings ascending:', strs.sort(firstCapAsc))
console.log('Sort strings descending:', strs.sort(firstCapDesc))
Now that is power.
Functions can be returned from other functions
We can also return a function from a function. This means we can use functions as factories to create other functions. Here is an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
function addToN (n) {
// This function takes an x and adds n to it
// n is "remembered" even though addToN has returned and left scope
return x => n + x
}
const addToFive = addToN(5)
const addToTen = addToN(10)
console.log(`addToFive(3) is ${addToFive(3)}`)
console.log(`addToFive(8) is ${addToFive(8)}`)
console.log(`addToTen(17) is ${addToTen(17)}`)
console.log(`addToTen(1) is ${addToTen(1)}`)
This is a pretty simple example, but more useful than you might think. Later, we'll talk about how we can “partially apply” some types of functions, and we'll use an add
function as an example. When we start maniuplating lists of items without using loops we'll see how incredibly powerful this ability to return a function from a function really is.
One thing to note is that under normal circumstances, when we call our addToN
function and pass it an argument, say 5
, the function creates a local (to the function) variable n
and assigns it the value 5
. We can then use it inside our addToN
function.
Normally when a function exits and returns a value, it releases all its local variables to be garbage collected. But because our addToN
function includes n
in the function it returns, we still have a pointer to that variable, which prevents it from being garbage collected until the function we returned is itself garbage collected.
In this way we can “capture” (or “close” on) a bit of state and reuse it over and over again, as we did with our addToFive
and addToTen
functions above. That is a very powerful feature of JavaScript: one you will use over and over and over again.
Functions can call functions, including themselves
Suppose we want to add up numbers from an array to get the sum. We could use a loop, but that's so last millennium. Let's try another approach using functions without loops. We'll see more of this in the next section, conveniently titled Loops are so last millennium!
We need to consider several different possibilities. For example, what if the list we want to add up is empty? What should we return?
You might say an Error
object, and in some instances that might be the best option. But in functional programming, we try to avoid throwing errors wherever possible. In functional programming, an Error
should mean the damn thing is broke and there ain't no fixing it.
So what is the sum of an empty list of numbers? Why not zero? In short:
sum([]) === 0 // should return true
What about a list with only one number in it, such as [5]
? Well, that's just 5
, isn't it? And 5
is the same as 5 + 0
, which is the same as 5 + sum([])
.
(Because the same input to a function always yields the same result, we can substitute sum([])
for 0
in that last example. If sum([])
could return different things at different times, then we could not make that substitution. This is the power of “pure” functions.)
So if we were to take the list, [5]
and pop the head off it (5
) and add it to the sum of the remaining list ([]
), then we'd get our new sum. But doesn't this work for a list of any length? Isn't the sum of [1, 2, 3]
just 1
plus the sum of [2, 3]
, which is just 2
plus the sum of [3]
, which is just 3
plus the sum of []
(which is 0
), hence the final sum is 3 + 0
or just 3
?
1 2 3 4 5
// Remember that sum takes an Array sum([1, 2, 3]) === sum([1]) + sum([2]) + sum([3]) + sum([]) === 1 + 2 + 3 + 0 === 6
So our sum
function could work by taking the first number in our list (the “head”) and adding it to the sum of the remaining items in the list (the “tail”). And it can do this by calling itself over and over again with the ever-shrinking tail until it hits an empty list in which case it returns zero and the sum is complete:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function sum (nums = [], total = 0) {
console.log(`Total is ${total}`)
// If nums is empty, return the total to stop the loop
if (nums.length < 1) {
console.log(`Returning the total of ${total}`)
return total
}
// Get the head (first number) and the tail (remaining numbers)
// The tail must approach the base case above so the loop terminates
const [head, ...tail] = nums
console.log(`Adding ${head} to ${total}`)
// The function calls itself with the remaining numbers and the new total
return sum(tail, total + head)
}
console.log(`↪ The sum of [] is ${sum([])}`)
console.log(`↪ The sum of [5] is ${sum([5])}`, '')
console.log(`↪ The sum of [5, 7, 9] is ${sum([5, 7, 9])}`)
We get into this more in the next section, but the ability of functions to call themselves means we never need to bother with for
, while
, or do...while
loops again.
And as you'll see, because there are many common operations for which we might use this superpower, naturally we've made built-in methods (and, even better, libraries of functions such as Ramda) to avoid having to write so many functions over and over again.
Reusability, right? High fives all around!
Don't be reaching outside the function to mess with shit
This is a bad and sometimes hard-to-break habit that many OO programmers have. Oh, look! We can put a variable in global scope, then mutate it from inside our function! Isn't that cool?
Um no. No it is not. Not at all. Now put on this pointy cap and sit facing the corner.
As we said above, functions take parameters and return a result, and that should be all they do. Functions should not be reaching outside the function to tweak any values or variables in a higher scope, such as the global scope.
Neither should anything in the outer scope change the return from our function. If it did, then we could not guarantee that for the same arguments we'll always get the same result.
The exception to this is when we are returning a function from a function, as discussed in Functions can be returned from other functions above. In that case, we are capturing the value of a variable in the outer function's scope and using it in the scope of the function returned.
But note that unless the outer function (our “function factory”) is itself reaching outside its scope, then as soon as it returns our new function with the captured variable, it goes out of scope. So, effectively, when we capture values in this manner we are not actually reaching “outside” the function. We're just closing on a value.
Of course, at some point we must reach outside. We need to have input and output from our program, unless we want to recreate the entire universe inside the program, which might take some time (and a hell of a lot of memory). So the big exception to this rule is input-output.
(We hate to break it to you, but Tron was fiction. Harsh, we know, but there you have it.)
In pure functional programming languages, the “impure” I/O functionality is isolated in its own modules to avoid nastying up the rest of the code.
In JavaScript, just make sure that if your function is changing something outside the function, or if it is affected by the state of something outside the function (other than an argument passed in), then you know what you're doing and why. Avoid it unless you absolutely have to do it, and then keep any “impure” functions isolated from the rest. Quarantine those spawn of Satan.
Here are examples of things you should never do. The squeamish among you may wish to cover your eyes lest this code haunt your nightmares. Don't say we didn't warn you. Enter the dark side...
Warning: Professional programmer working in a sandboxed environment. Do not try this at home!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// For the love of all that is sacred and true, DO NOT DO THIS!
// Do not let the evil "let" into your code!
// Be constant to "const" (constant === faithful, get it?)
let x = 'x'
function doEvil (y) {
x = y // Aieeee! Mutant variable!
}
console.log(`Oh, look what doEvil('y') returns: ${doEvil('y')}`)
console.log(`And x is now ${x}! Oh, the humanity!`)
console.log(`doEvil changed something other than through its result. That's just so wrong!`)
// More evil!
let i = 1 // Global, ack!
function inc (n) {
return n + i // Reaching out to the global. Ugh.
}
console.log(`The inc function increments by 1: inc(5) is ${inc(5)}`)
i = 5 // The global mutates. Ack!
console.log(`Or does it? inc(5) is ${inc(5)}. WTF?`)
console.log('Calling inc(5) at different times got different results. Ack! No! No! No!')
What's an example of when it's OK to use an impure function to affect something other than by the result? How about console.log
? It returns undefined
no matter what the input, and it reaches out to the console and changes it, and that's outside our function, right? But this is input/output, which we already admitted is an exception.
In general, input/output aside (e.g., logging), no exceptions.
Make copies when passing by reference
Often when programmers are just starting to work with functional JavaScript, they will mutate (change) a value from inside a function without realising it.
A “feature” of JavaScript is that primitive values, such as numbers, strings, and booleans, are copied when they are passed as arguments to a function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let name = 'Bob'
function toUpper (name) {
// `toUpperCase` doesn't mutate in place, but returns a new string, as it should
// Here we're overwriting the local `name` variable with an uppercase version
name = name.toUpperCase()
// We return the VALUE of the updated LOCAL variable
// The outer `name` is unaffected
return name
}
console.log(`toUpper(name) yields ${toUpper(name)}`)
console.log(`But name is still ${name}`)
// Now let's mutate the outer variable
name = name.toUpperCase() // Why we don't use "let"
console.log(`But now it isn't: ${name}. Oh, Bob!`)
As we see above, simple values are “passed by value”, which means they are copied into the function scope. But more complex values, such as arrays and objects, are not copied. Instead, a reference to the original is passed.
(This is true in many languages, not just JavaScript.)
Now, if we mutate (change) that variable in the function scope, we are actually reaching outside the scope to change the original value. This is a big no-no:
We wrote this code so you don't have to.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const person = {
name: 'Bob',
position: 'Tester'
}
// DON'T DO THIS!
function updateName (person, name) {
person.name = name
return person
}
console.log(`updateName(person, 'Bobby') returns person.name of '${updateName(person, 'Bobby').name}'`)
console.log(`Now our outer person.name is... wait for it... '${person.name}'.`)
console.log(`That's just not right.`)
So how do we avoid this? We can use the spread operator ...
to make a copy of our referenced object and then update the copy. Easy peasy!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const person = {
name: 'Bob',
position: 'Tester'
}
function updateNameCorrectly (person, name) {
return {
...person, // copies all of the key-value pairs in person to a new object
name // overwrites the name value in the new object
}
}
console.log(`updateName(person, 'Bobby') returns person.name of '${updateNameCorrectly(person, 'Bobby').name}'`)
console.log(`Now look what happened to our outer person.name: '${person.name}'.`)
console.log(`No change! Hooray! We're GENIUSES! Big pay rise? No? Rats.`)
Functions can remember things for you
We already discussed this above in Functions can be returned from other functions, but it's worth revisiting.
Consider the addToN
function we created above that took a number n
and returned a function. The function returned took a number and returned the sum of that number and n
. We could rename that function 'add
' and use arrow functions to keep it as short and sweet as possible:
1
2
3
// add takes an x and returns a function
// that takes a y and returns x + y
const add = x => y => x + y
Now let's take it to the next level. Ready? Let's create a function that lets us pipeline other functions.
Then we'll create add
, subtract
, multiply
, and divide
functions that allow us to pass just one argument and get back a function that takes the second argument and does the arithmetic.
Then we'll pipeline a few of them together and see if it works:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Beauty, eh?
function pipe (f, ...fs) {
if (fs.length < 1) { return f } // No tail? Return the function
// Return a function that takes arguments
// applies `f` to them, then passes that result
// the the function returned from calling pipe
// again on the remaining functions
return (...args) => pipe(...fs)(f(...args))
}
// add is a function that takes x
// and returns a function that takes y
// adds it to x, and returns the sum
const add = x => y => y + x
// These work the same way
const subtract = x => y => y - x
const multiply = x => y => y * x
const divide = x => y => y / x
// Equivalent to `y => ((((y + 5) - 7) * 10) / 4)`
const doTheMath = pipe(add(5), subtract(7), multiply(10), divide(4))
console.log(`doTheMath(12) is ${doTheMath(12)}`)
console.log(`((((12 + 5) - 7) * 10) / 4) is ${((((12 + 5) - 7) * 10) / 4)}`)
console.log('doTheMath(12) === ((((12 + 5) - 7) * 10) / 4)')
console.log(doTheMath(12) === ((((12 + 5) - 7) * 10) / 4))
Holy crap! Wasn't this supposed to be easy? What the heck is this supposed to do:
1
2
3
4
5
function pipe (f, ...fs) {
if (fs.length < 1) { return f }
return (...args) => pipe(...fs)(f(...args))
}
Well, let's take it apart a little at a time. First, there's this line:
function pipe (f, ...fs) {
This declares a function called pipe
, assigns the first argument to a local variable called f
(the parameters are functions, so f
makes sense, right?), then gathers up the remaining arguments into an array of functions called fs
.
(Calling the head of a list x
and the tail xs
, or y
and ys
, or, as here, f
and fs
is a common way to denote the head and tail in FP. Think of it as similar to element
and elements
. Remember that the head
is the first value in the array while the tail
is an array of the remaining values. That's important!)
We're assuming, of course, that all the arguments to pipe
are functions, as the point of pipe
is to take one or more functions and return a function that applies them sequentially to a set of arguments.
So in our example, the call to pipe
looks like this:
pipe(add(5), subtract(7), multiply(10), divide(4))
Remember that pipe
is going to call itself, so this is the first call to pipe
.
So in (f, ...fs)
, f
is assigned the value add(5)
which is the same as y => y + 5
, right? And fs
is an array of the remaining arguments: [subtract(7), multiply(10), divide(4)]
.
Next we hit the guard: if (fs.length < 1) { return f }
. But our current fs
has a length of 3, so we skip the guard. Now we hit this line:
return (...args) => pipe(...fs)(f(...args))
Oh, joy! But it's not as tricky as it looks at first. Because our functions always return the same thing for the same input, we can do some substitution, right?
The first thing we know is that the value of f
is y => y + 5
(a function), so we can substitute that in for (...args)
and f(...args)
(note that ...args
here is just y
, right?):
return y => pipe(...fs)(y + 5)
We also know that the value of fs
is [subtract(7), multiply(10), divide(4)]
, so we can substitute that as well:
return y => pipe(subtract(7), multiply(10), divide(4))(y + 5)
Remember that the spread operator ...
“spreads” the array of functions out into individual arguments.
Note that pipe
returns a function, and we're calling that function with y + 5
, which is the result of applying the first function in the pipe (add(5)
) to y
, whatever y
is. What we've done here is just substituted away one function call from our pipe.
Note: It is important to remember that pipe
returns a function. We're not applying our outermost function yet, we're just nesting the four functions.)
What we're trying to accomplish is the equivalent of this:
1
2
3
4
5
6
7
8
9
10
11
12
const doTheMath = y => {
return divide(
4,
multiply(
10,
subtract(
7,
add(5, y)
)
)
)
}
After the second call to pipe
, we can simplify further:
return y => pipe(multiply(10), divide(4))((y + 5) - 7)
Now we've substituted away two functions from our pipe. And after the third call:
return y => pipe(divide(4))(((y + 5) - 7) * 10)
And one last time:
return y => (((y + 5) - 7) * 10) / 4
Which means our final result is this after we've subsituted the bodies of our functions for the partially-applied function calls in the pipe:
const doTheMath = y => (((y + 5) - 7) * 10) / 4
This is what pipe
actually returns: y => (((y + 5) - 7) * 10) / 4
. It's pretty straightforward arithmetic.
So we can see that the value of doTheMath
is a function that takes the y
that add(5)
is expecting, calls it with y
, then takes the value returned from that call and calls subtract(7)
with it, and so on through the entire list of functions, returning the final result:
doTheMath(12) // passes 12 to
add(5)(12) // returns 17, passed to
subtract(7)(17) // returns 10, passed to
multiply(10)(10) // returns 100, passed to
divide(4)(100) // returns 25, which is returned from doTheMath(12)
Combining functions together can get quite complex, but if we name the functions well, and we use pure functions that always return the same result for the same arguments, then we can substitute again and again, simplifying our code, until we see what's really happening.
Note that the first function passed to pipe
can take any number of parameters, but as each succeeding function takes as its argument the return value of the previous function, all functions passed to pipe
after the first one must expect only one parameter.
Now let's talk a bit more about variables and why we never change them once they've been assigned: Variables are “set and forget”.
We'll also get more into combining functions later in “We can has pipelines!”
Errors, bugs, suggestions, questions? Contact Charles Munat.