Functional Programming (Part 4): Functional composition

Ahmad M. Hawwash
Bits and Pieces
Published in
6 min readJun 13, 2022

--

Where fun meets productivity!

Photo by Tim Mossholder at Pexels

This article is a part of a series that talks about “Functional Programming”

In the previous article in this series we discussed a very useful pattern; Currying and how we can use it, where we also utilised it to apply separation of concerns very elegantly. In this article we’ll be talking about Composition.

Table of contents

  • What is Functional Composition
  • How composition works
  • compose vs pipe
  • Examples
  • Why Composition
  • Conclusion

What is Functional Composition

Composition is the process of composing small units into bigger units that solve bigger problems. Where Input of one function comes from the output of previous one.

For example, if we want to build a castle 🏰 we’ll have to compose bricks 🧱

How Composition Works

As the definition states

Input of one function comes from the output of another function”.

If we build this mathematical function (f ∘ g)(x) = f(g(x)), which composes 2 small functions, it’ll look like this:

const compose = (f, g) => (x) => f(g(x))

The compose function composes f and g where the output of g will be forwarded as input of f.

Let’s give it a try:

const getAge       = (user) => user.age
const isAllowedAge = (age) => age >= 30

We have getAge and isAllowed small functions. Let’s compose them:

const user = { name: 'John', age: 35 }
const isAllowedUser = compose(
isAllowedAge,
getAge
)
isAllowedUser(user) // true

Note: compose flows from bottom to top (right to left). We pass user to the composite isAllowedUser, then user goes through getAge first, then the output goes to isAllowedAge.

Compose vs Pipe

pipe is very similar to compose, they have the same purpose. Both are here to chain functions, however they have different implementations and workflows.

Implementation

compose is implemented like this:

const compose = 
(...fns) =>
(x) => fns.reduceRight((acc, fn) => fn(acc), x)

pipe is implemented like this:

const pipe = 
(...fns) =>
(x) => fns.reduce((acc, fn) => fn(acc), x)

Where the only difference here is reduce instead of reduceRight. Which only affects the flow of the data…

Flow

compose‘s flow is from bottom to top ↑(right to left ←).

pipe‘s flow is from top to bottom ↓ (left to right →)

Example

Let’s say that we have 3 functions, f, g and h

If we use compose:

compose(f, g, h)
← ← ←

The evaluation order will go h, g then f (right to left), and each output is forwarded to the next function.

If we use pipe:

pipe(f, g, h)
→ → →

The evaluation order will go f, g then h (left to right), and each output is forwarded to the next function.

Which should you use?

They don’t differ much. Both will solve the same problem.

But just to highlight the difference. compose is just closer to the mathematical notation of (f ∘ g)(x) = f(g(x)), where pipe is often easier to read in evaluation order.

So personally, I prefer pipe because it is more natural.

Examples:

Note: as a personal preference I’ll be using pipe in the examples.

Let’s say we need to build a simple price calculator, where we can apply:

  1. Discount
  2. Coupon
  3. Tax (default = 30%)
  4. Service fees (default = 10)
  5. Weight-based shipping cost

Let’s first design the API of the price calculator based on the given requirements

const priceCalculator = (
taxPercentage = 0.3,
serviceFees = 10,
price,
discount,
percentCoupon,
valueCoupon,
weight,
$PerKg
) => {
/*logic*/
}

We defaulted tax to 30% and serviceFees to 10$. Waiting for the rest of the params

The bad way (non compositional)

Let’s build it as an atomic mathematical equation:

const priceCalculator = (
taxPercentage = 0.3,
serviceFees = 10,
price,
discount,
percentCoupon,
valueCoupon,
weight,
$PerKg
) => {
return (
price
- (price * percentCoupon)
- discount
- couponValue
+ (weight * $PerKg)
+ serviceFees
) * (1 + taxPercentage)

}

It does the job. But it has very poor reading, testing, debugging and maintenance experiences.

Let’s do it the good way (composition approach)

Let’s compose…

const priceCalculator = (
taxPercentage = 0.3,
serviceFees = 10,
price,
discount,
percentCoupon,
valueCoupon,
weight,
$PerKg
) => {
const applyTax = (val) => val * (1 + taxPercentage)
const applyServiceFees = (val) => val + serviceFees
const applyPercentCoupon = (val) => val - val * percentCoupon
const applyValueCoupon = (val) => val - valueCoupon
const applyDiscount = (val) => val - discount
const applyShippingCost = (val) => val + weight * $PerKg
return pipe(
applyPercentCoupon,
applyDiscount,
applyValueCoupon,
applyShippingCost,
applyServiceFees,
applyTax
)(price)

}

This looks so much cleaner, more testable, debuggable and maintainable. All due to the modular mindset we’re adapting to.

We split each operation to be living on its own, then we pipe them and apply price to the piped functions.

Give it a try

priceCalculator(10) // NaN

We got a NaN and that’s unexpected, right?! How can we fix this?

Let’s debug first

Let me introduce a very useful utility to debug chains (pipe and compose); inspect function. It’s very simple. It only logs what it gets, and returns it.

const inspect = (tag) => (x) => {
console.log(`${tag}: ${x}`)
return x
}

Now let’s add that to our chain of functions

const priceCalculator = (
taxPercentage = 0.3,
serviceFees = 10,
price,
discount,
percentCoupon,
valueCoupon,
weight,
$PerKg
) => {
const applyTax = (val) => val * (1 + taxPercentage)
const applyServiceFees = (val) => val + serviceFees
const applyPercentCoupon = (val) => val - val * percentCoupon
const applyValueCoupon = (val) => val - valueCoupon
const applyDiscount = (val) => val - discount
const applyShippingCost = (val) => val + weight * $PerKg
return pipe(
inspect('price'),
applyPercentCoupon,
inspect('after applyPercentCoupon'),
applyDiscount,
inspect('after applyDiscount'),
applyValueCoupon,
inspect('after applyValueCoupon'),
applyShippingCost,
inspect('after applyShippingCost'),
applyServiceFees,
inspect('after applyServiceFees'),
applyTax
)(price)
}

The results would look something like

priceCalculator(10)
// price: undefined
// after applyPercentCoupon: NaN
//...

Oh! The price is undefined, haha that’s because the price is the 3rd parameter, and we’re passing it first instead!

Let’s fix it very quickly

I’ll just let it accept a single object {} instead of multiple, like:

const priceCalculator = ({
taxPercentage = 0.3,
serviceFees = 10,
price,
discount,
percentCoupon,
valueCoupon,
weight,
$PerKg
}) => {...}

And we use it like this

priceCalculator({ price: 10 })
// price: 10
// after applyPercentCoupon: NaN
//...

We still get NaN, but this time for a different reason. Because percentCoupon is being used while it’s undefined too.

So let’s fix it by defaulting all parameters (except price)

const priceCalculator = ({
taxPercentage = 0.3,
serviceFees = 10,
price,
discount = 0,
percentCoupon = 0,
valueCoupon = 0,
weight = 0,
$PerKg = 0
}) => {...}

So now if we use it again, we’ll get a result…

priceCalculator({ price: 10 }) // 26

That’s how composition allows us to test and fix our code easier and quicker, just by inspecting the areas of the pipeline that we suspect.

In Real Life

The problem we debugged was very simple (for a reason). When we go on bigger scale modules, things go nastier quickly and harder to inspect when we use big atomic functions. Splitting functions into smaller ones makes them easier to debug, test, maintain, and develop functions.

Why Composition

Composition is about composing smaller modules into bigger ones. Where we think in a modular way (that’s enforced by composition), we’ll enhance:

  1. Modularity mindset
  2. Testability
  3. Debuggability
  4. Maintainability

Conclusion

Composition is a way of building big modules out of smaller ones, it makes our code more modular. Thus it’s easier to debug, test, maintain, reuse and even more fun to develop functions. Instead of the old way of jamming all the code into one single area.

Thanks a lot for taking time reading through this article ❤️ I’m cooking the next ones in the series. Please let me know what you think in the comments about this article or the series.

This is an article in a series of articles talking about Functional Programming

In this series of articles we cover the main concepts of Functional Programming. At the end of the series, you’ll be able to tackle problems in a more functional approach.

This series discusses:

0. A Brief Comparison Between Programming Paradigms

  1. First Class functions
  2. Pure functions
  3. Currying
  4. Composition (this article)
  5. Functors
  6. Monads

References:

  1. https://github.com/ramda/ramda/issues/1642
  2. https://en.wikipedia.org/wiki/Function_composition_(computer_science)#JavaScript

--

--

twitter: @AhmadMHawwash. Frontend engineering consultant @Mirado Stockholm. With interest in JS, TS, FP, React, Nextjs, Clean Code, Clean Architecture…