How to FP

ReusabilityVariablesLoopingPipelines

Home»Bottom-up»Pipelines

We can has pipelines!

Everything we've discussed up to this point has been prologue. The true power of functional programming comes from combining functions to create a kind of function pipeline.

Here is the most important thing for you to understand about piping data from one function to another:

  • The first function in a pipeline can take any number of parameters.
  • Each succeeding function in the pipeline must take only a single parameter.

The reason for this should be obvious: every function after the first one is getting its input from the output of the preceeding function. As a function can only return a single value, there is no way to pass more than one argument to the next function in the pipe. Right?

Show Evil Terminology

Of course, functions in a pipe other than the first function can accept more than one parameter. It's just that only a single argument will be passed from the preceeding function. But if the parameters after the first one have suitable default values, that can still work.

But pipelining functions will become much more useful to us if there is a way to partially apply functions so that we pre-load them with the other arguments they need. We can do this by creating functions that can return other functions if they are called with fewer arguments than the number of parameters they expect.

Show Evil Terminology

Here is where we separate the pros from the novices. We mentioned default values for the extra parameters above, and if you think that way, then your inclination will be to make the data that we intend to pass down the pipe the first parameter, because that's the one that will be passed by the preceeding function, right?

But this means that the only way to fill the remaining arguments is with default values, which is pretty limiting.

But if we can apply fewer arguments than there are parameters to a function, and get back a function that remembers those arguments and accepts the remaining parameters, then we want the parameter that will be chained to be the last parameter, not the first. We'll see this when we get to using pipe and compose below.

This is where the Underscore and Lodash libraries went wrongfixed in Lodash/FPand what Ramda and Sanctuary got so right. As you can see from the butt-ugly documentation for the FP module (compared to the love given to the non-FP module), the folks at Lodash still can't admit that they did it all wrong. Sigh...

In Underscore and Lodash, the argument we want to pass down the pipe is passed as the first parameter, which means there is no way to set any remaining parameters, except by default value. Underscore and Lodash get around this by forcing you to add a chain function callan absolute code stinkand they expect you to pipe function calls by dotting them together.

Mit der Dummheit kämpfen Götter selbst vergebens Friedrich Schiller.

Against stupidity the gods themselves contend in vain.

Ramda and Sanctuary, in contrast, allow you to partially apply functions so that only the last parameter remains unfilled, and then use pipe or compose to chain them together to create a new function that takes any number of parameters, passes the data down the chain of functions, and returns the final result.

Enough chat! Let's cut to the chase!

Use curry to make functions that make functions

The curry function takes a function that expects one or more parameters and turns it into a function that allows partial application of those parameters, ideally in any combination (but preserving order). Like this:

1
2
3
4
5
6
7
8
9
10
11
import { curry } from 'ramda'

const doSomeArithmetic = curry((a, b, c, d) => a + b * c - d)

console.log(`doSomeArithmetic(10, 7, 5, 3) is ${doSomeArithmetic(10, 7, 5, 3)}`)
console.log(`doSomeArithmetic(10, 7, 5)(3) is ${doSomeArithmetic(10, 7, 5, 3)}`)
console.log(`doSomeArithmetic(10, 7)(5, 3) is ${doSomeArithmetic(10, 7, 5, 3)}`)
console.log(`doSomeArithmetic(10)(7, 5, 3) is ${doSomeArithmetic(10, 7, 5, 3)}`)
console.log(`doSomeArithmetic(10, 7)(5)(3) is ${doSomeArithmetic(10, 7, 5, 3)}`)
console.log(`doSomeArithmetic(10)(7)(5, 3) is ${doSomeArithmetic(10, 7, 5, 3)}`)
console.log(`doSomeArithmetic(10)(7)(5)(3) is ${doSomeArithmetic(10, 7, 5, 3)}`)

But with Ramda's curry function, we can also use the __ placeholder for some really clever FP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Ramda's double underscore __ works like the _ in FP
import { __, curry } from 'ramda'

const doSomeArithmetic = curry((a, b, c, d) => a + b * c - d)

// This is da bomb!
console.log(`doSomeArithmetic(10)(7)(5)(3) is ${doSomeArithmetic(10, 7, 5, 3)}`)

// Arguments are supplied to the first empty parameter, so do observe ordering
console.log('doSomeArithmetic(__, __, __, 3)(10, 7, 5) is '
  + `${doSomeArithmetic(__, __, __, 3)(10, 7, 5)}`)
console.log('doSomeArithmetic(__, 7, __, 3)(10, 5) is '
  + `${doSomeArithmetic(__, 7, __, 3)(10, 5)}`)
console.log('doSomeArithmetic(__, __, __, 3)(10, __, 5)(7) is '
  + `${doSomeArithmetic(__, __, __, 3)(10, __, 5)(7)}`)

Vanilla JS is quite a bit more difficult. The easiest way is simply to be functional and create our own curry function:

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
30
31
32
33
34
35
36
37
38
39
40
// curryN takes the number of parameters and the function
const curryN = (n, f) => (...args) => // return a function that takes some arguments
  args.length === n  // if all the arguments have been supplied
    ? f(...args)     // then call the function with the args and return the result
    : curryN(
      n - args.length,
      (...newArgs) => f(...args, ...newArgs)
    )                // else recurse for the remaining arguments

const curry = f => curryN(f.length, f) // Create a closure on the number of parameters
  
// A function that takes 3 parameters
const uncurried = (x, y, z) => ([x, y, z])

// Try applying it without currying
console.log(`uncurried(1, 2, 3) is ${JSON.stringify(uncurried(1, 2, 3))}`)

// Oops
try {
  console.log(`uncurried(1, 2)(3) is ${JSON.stringify(uncurried(1, 2)(3))}`)
  console.log(`uncurried(1)(2, 3) is ${JSON.stringify(uncurried(1)(2, 3))}`)
  console.log(`uncurried(1)(2)(3) is ${JSON.stringify(uncurried(1)(2)(3))}`)
} catch (e) {
  console.log(`Uh, oh: ${e}`)
}

// Now let's try again, but with our function curried properly
const curried = curry((x, y, z) => ([x, y, z]))

// Everything works
console.log(`curried(4, 5, 6) is ${JSON.stringify(curried(4, 5, 6))}`)
console.log(`curried(4, 5)(6) is ${JSON.stringify(curried(4, 5)(6))}`)
console.log(`curried(4)(5, 6) is ${JSON.stringify(curried(4)(5, 6))}`)
console.log(`curried(4)(5)(6) is ${JSON.stringify(curried(4)(5)(6))}`)

// We can partially apply our function to create new functions
// that "remember" one or more arguments
const prependSeven = curried(7)
const prependSevenAndEight = prependSeven(8)
console.log(`prependSevenAndEight(9) is ${JSON.stringify(prependSevenAndEight(9))}`)

Lovely. But our own curry function does not allow the use of the __ placeholder. We could add that, but now our function becomes quite a bit more complexif you check the Ramda source code, they break it out into four different functions and a constant.

Our suggestion: If you want to do serious functional programming with these sorts of functions, just use Ramda or Sanctuary.

With a good build system, e.g., Webpack, you should be able to tree shake out any unused code, so you don't bulk up your compiled code.

Now to see what we can do with curried functions.

(Note: Pretty much every function included in Ramda is pre-curried, so you can partially apply any of them. And you have the curry function to curry your own functions. Just remember, as Stan Lee reminds us, that with great power comes great responsibility.)

Use pipe or compose to combine functions into pipelines

The pipe and compose functions do the same thing, just in different directions. Each takes functions as parameters (in Sanctuary they are passed in a single parameter as an array). Each applies the functions one after the other. The difference is: pipe applies them left to right, and compose applies them right to left.

The reasons for this are historical. Use whichever makes more sense to you.

Consider a situation such as this:

1
2
3
4
5
const combinedFunctions = (a, b, c) => outerFunction(
  middleFunction(
    innerFunction(a, b, c)
  )
)

This is essentially what pipe and compose do, but you don't need to wrap innerFunction, middleFunction, and outerFunction in a yet another function as we do above. You just pass them to pipe or compose and you get a function back.

The pipe function takes the function arguments in the order that they will be applied. In the above, nested example, the functions will be applied from the innermost to the outermost, right? So our pipe would look like this:

const combinedFunctions = pipe(innerFunction, middleFunction, outerFunction)

In contrast, the compose function follows the nested order that we see them in the above example (outermost to innermost):

const combinedFunctions = compose(outerFunction, middleFunction, innerFunction)

But the functions are still applied in the same order: innerFunction to middleFunction to outerFunction.

So innerFunction can take as many parameters as it needs, but middleFunction and outerFunction will each be passed only the result of the previous function, so they can only operate on a single parameter.

Suppose I used the (curried) Ramda functions, add, subtract, multiply, and divide. Each takes two parameters, so I will need to supply one of them for each (except, possibly, the first function in the pipeline).

I want to create a function that takes a number, adds five, subtracts that sum from twenty, multiplies the difference by nine, and divides that product by sixty, in that order.

Yes, yes, yes. We know. This is an absurd example and we'd just use the +, -, *, and / operators. But it's about the principle! So let's run with it.

Here's 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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { __, add, compose, divide, multiply, pipe, subtract } from 'ramda'

// The hard-to-read way
const doTheMathNested = x => divide(
  multiply(
    9,
    subtract(
      20,
      add(5, x)
    ),
  ),
  60
)

// add and multiply are commutative, so ordering of parameters doesn't matter
// subtract and divide are not cummutative, so ordering counts
// Here we want to divide our number by 60, not divide 60 by it, so we need the __ placeholder
// compose runs right to left, which is more traditional, but possibly harder to read
const doTheMathByCompose = compose(
  divide(__, 60),
  multiply(9),
  subtract(20),
  add(5)
)

// pipe runs left to right, which may be easier to comprehend
const doTheMathByPipe = pipe(
  add(5),
  subtract(20),
  multiply(9),
  divide(__, 60)
)

// ((20 - (7  + 5)) * 9) / 60
console.log(`doTheMathNested(7) is ${doTheMathNested(7)}`)
console.log(`doTheMathByCompose(7) is ${doTheMathByCompose(7)}`)
console.log(`doTheMathByPipe(7) is ${doTheMathByPipe(7)}`)
console.log(`((20 - (7  + 5)) * 9) / 60 is ${((20 - (7  + 5)) * 9) / 60}`)

Here's a more complex example in which we use tap to tap into the pipe and use a side effect to log the value to the console. Remember that tap passes the value unchanged, so it is always and only used for side effects such as logging. Do not use tap to mutate program state!

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
30
import { __, add, curry, map, pipe, reduce, tap } from 'ramda'

const binaries = ['1', '100', '1001', '10000', '11001']

// We can curry the JS parseInt function
// Then partially apply it by supplying the second parameter
// parseBinary takes a binary number string and converts it to an integer
const parseBinary = curry(parseInt)(__, 2)

// We can use tap to log out the value at any point in the pipe
// THIS IS A SIDE EFFECT! DO NOT USE tap TO CHANGE STATE!
const sumBinary = pipe(
  tap(console.log),
  map(parseBinary),    // map the binary strings to integers
  tap(console.log),
  map(Math.sqrt),      // map the integers to their square roots
  tap(console.log),
  reduce(add, 0),      // add the square roots up to get the sum
  tap(console.log),
  n => n.toString(2),  // convert the integer sum to a binary number string
  tap(console.log)
)

console.log(`binaries is [${binaries}]`)
console.log('Tapping the pipe:')

const sum = sumBinary(binaries)

console.log('Pipe complete')
console.log(`The sum of the squares of the binaries is binary ${sum}`)

We can also use Ramda's then and otherwise to handle async behaviour with Promises. See if you can figure out what this does:

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
30
31
32
33
34
35
36
import { otherwise, pick, pipe, then } from 'ramda'

const makeQuery = email => ({ query: email })
const useDefault = () => ({
  nameGiven: 'Aleister',
  nameFamily: 'Crowley',
  email: 'aleister@goldendawn.com'
})

// Use setTimeout to simulate an asynchronous process
const fetchProfile = options => new Promise((resolve, reject) => {
  options.query === 'bob@slack.com'
    ? setTimeout(() => resolve({
        nameGiven: 'Bob',
        nameFamily: 'Dobbs',
        email: 'bob@slack.com'
      }), 3000)
    : setTimeout(() => reject({
        error: 'Bad email address'
      }), 6000)
})

// Returns a promise
const getProfleFromEmail = pipe(
  makeQuery,                               // convert email to query
  fetchProfile,                            // use query to fetch profile
  otherwise(useDefault),                   // default profile if fetch fails
  then(pick(['nameGiven', 'nameFamily']))  // extract just the names from the profile
)

// The first of these fails and gets the default profile
// The second passes and retrieves the profile for bob@slack.com
// Note the delay and the order of output
console.log('Wait for it!')
getProfleFromEmail('joe@chicago.com').then(console.log) // resolves in 6 seconds
getProfleFromEmail('bob@slack.com').then(console.log)   // resolves in 3 seconds

Think in terms of state!

In the simplest sense, the state of your application is anything that could conceivably be different at time t2 than it was at time t1: valuet1 !== valuet2.

When we work in a functional paradigm, our concerns about state are twofold:

  1. Do not overwrite state: make a new copyoverwriting state is destroying history
  2. Do not share state between processes: keep processes isolatedshared state leads to race conditions and bugs

We can't always keep these promises, but we should do our utmost to live up to these best practices.

The two most important practices for handling state properly are immutability and keeping our functions pure.

Immutability means that once we create a variable, we never reassign it or change it. It is frozen. If we need to change it, we make a new copy that reflect the change. This means that we never lose track of our previous state (until we want to). It also means that if we know what value a variable had when it was assigned, then we know what value it has now. It never changes.

Pure functions help us to isolate processes to avoid sharing state. They also help with immutability by always returning a copy rather than mutating in place. A pure function, if you recall, is a function that always returns the same result for the same arguments, and does not affect the program in any other way: no reaching out from within a function; no reaching into functions to muck about.

How can we avoid mutating variables? The best way is to use functions instead of loops, conditionals, and other constructs.

For example, suppose that I want to assign a variable colour differntly depending on the value of variables a and b:

Lasciate ogne speranza, voi ch'entrate!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// DO NOT DO THIS!
function getColourTheBadWay (a, b) {
  let colour

  if (a && b) {
    colour = 'red'
  } else if (a) {
    colour = 'yellow'
  } else if (b) {
    colour = 'orange'
  } else {
    colour = 'black'
  }

  return colour
}

console.log(`getColourTheBadWay(true, true) is ${getColourTheBadWay(true, true)}`)
console.log(`getColourTheBadWay(true, false) is ${getColourTheBadWay(true, false)}`)
console.log(`getColourTheBadWay(false, true) is ${getColourTheBadWay(false, true)}`)
console.log(`getColourTheBadWay(false, false) is ${getColourTheBadWay(false, false)}`)

A much better style is this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getColour (a, b) {
  if (a && b) { return 'blue' }
  
  if (a) { return 'cyan' }
  
  if (b) { return 'magenta' }
  
  return 'white'
}

console.log(`getColour(true, true) is ${getColour(true, true)}`)
console.log(`getColour(true, false) is ${getColour(true, false)}`)
console.log(`getColour(false, true) is ${getColour(false, true)}`)
console.log(`getColour(false, false) is ${getColour(false, false)}`)

Not only to we get rid of let, assigning a variable altogether, and mutation, but our code is cleaner, more readable, and more succinct. We also lose the else construct. The function returns the moment the correct answer is found. The use of return is very clear.

What's more, with the use of return the switch statement, formerly something to avoid, becomes quite useful as a sort of switchboard or pattern matcher:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
import { sort } from 'ramda'

function selectComparator (type) {
  switch(type) {
    case 'DESC':
      return (a, b) => b - a
    case 'LENGTH_ASC':
      return (a, b) => a.length - b.length
    case 'LENGTH_DESC':
      return (a, b) => b.length - a.length
    default:
      return (a, b) => a - b
  }
}

const names = ['Bob', 'Carol', 'Ted', 'Alice', 'Samson', 'Delilah']

// Array.sort sorts in place mutating the array
// DO NOT USE THIS!
console.log('names is:', names)
console.log('names.sort() is', names.sort())
console.log('names is now:', names)
console.log('Bad sort. Bad! Let\'s try again:')

// USE THIS
console.log(
  `sort(selectComparator('LENGTH_DESC'), names) is:`,
  sort(selectComparator('LENGTH_DESC'), names)
)
console.log('names is unchanged:', names)
console.log(
  `sort(selectComparator('LENGTH_ASC'), names) is:`,
  sort(selectComparator('LENGTH_ASC'), names)
)
console.log(
  `sort(selectComparator('DESC'), [2, 5, 3, 9, 1]) is:`,
  sort(selectComparator('DESC'), [2, 5, 3, 9, 1])
)
console.log(
  `sort(selectComparator('ASC'), [2, 5, 3, 9, 1]) is:`,
  sort(selectComparator('ASC'), [2, 5, 3, 9, 1])
)

You will see this pattern over and over again.

When we mutate variables or reassign them, and when we reach outside of functions to change state in the global context, we create chaos. Anarchy! Who knows what is changing what else and when?

It's like spaghetti code. You find yourself tracing from line to line and function to function and even file to file just to figure out where something was mutated.

Avoid the anarchy! A well-regulated state, being necessary to the security of your code base, the right of variables to be free of mutation and functions to be free of side effects shall not be infringed.

Functional programming gives you all the tools to build fast, efficient, understandable, and error-free code. Embrace the functional style.

Things fall apart; the centre cannot hold;
Mere anarchy is loosed upon the world...

What rough beast, indeed.

Errors, bugs, suggestions, questions? Surplus rye whiskey? Contact Charles Munat.