How to save 100s of debugging hours with fp-ts

Part 1: combining functions in sequence with flow and pipe

Liam Butler-Lawrence
Candle Finance
4 min readNov 21, 2022

--

Image of surrealist corn maze

🧐 Should I even care about functional programming?

How many hours have you spent debugging something and thinking “how did it even get into this state?” Or “how did I miss that edge case?” Or “if only I could refactor this…”

But that’s just wishful thinking, right?

Well… no. With functional programming:

  • every error, default case, and unexpected state is explicitly handled.
  • parallelizing tasks only requires adding a single line.
  • every function can easily be factored out into smaller components.

Picture your app or service as a corn maze. Using traditional programming is like trying to design it from inside the maze—hacking away one step at a time.

But using functional programming is like designing it from an airplane, where you can see how each path connects to make a simple, clear whole, which does exactly what you want.

📚 But don’t I need to learn Haskell or something?

Nope. fp-ts is an actively maintained TypeScript library with thousands of stars on GitHub.

It adds complete functional programming to TypeScript, using simple syntax you’re already familiar with.

Installing it couldn’t be simpler:

npm i fp-ts

⚔️ But isn’t functional programming complicated?

It really isn’t! If you know how to write code, you can understand the core concepts behind functional programming.

  1. A function stores knowledge of how to perform some task.
  2. Combining functions in various ways (e.g. in sequence or in parallel) lets you create new functions that perform more complicated tasks
  3. Your entire app or service is really just one big function, made up of smaller functions combined in different ways

💡As simple as it gets…

flow lets you combine functions by stringing them together in sequence. Whatever each function returns is passed as a parameter to the next function in line.

Let’s imagine we’re writing a check to determine if we support shipping to a user’s address. Here’s a traditional implementation.

const isSupportedAddress = (address: string) => {
const parsedAddress = parseAddress(address)
const countryCode = getISOCountryCode(parsedAddress.country)

return ['US', 'MX', 'CA'].includes(countryCode)
}

Notice the unnecessary variables and redundant naming.

And here’s the same code using flow:

import { flow } from 'fp-ts/function'

const isSupportedAddress = flow(
parseAddress,
_ => _.country,
getISOCountryCode,
['US', 'MX', 'CA'].includes
)

The actual functions we’re stringing together are front and center, with no extraneous information or syntax.

🪄 Syntax sugar

pipe is a version of flow that takes an extra first parameter, which it uses to call the combined function. So instead of returning a new function, pipe just returns a new value.

Let’s say you’re working inside an existing function, and you want to use flow to simplify some logic, but the syntax ends up being a little hard to read:

import { flow } from 'fp-ts/function'

const submitShippingOrder = (user: UserModel) => {

const addressIsSupported = flow(
parseAddress,
_ => _.country,
getISOCountryCode,
['US', 'MX', 'CA'].includes
)(user.address)

if (addressIsSupported) {
// submit order
}
}

Notice that we’re calling the combined function immediately after creating it, by passing it user.address.

Instead, we can use pipe:

import { pipe } from 'fp-ts/function'

const submitShippingOrder = (user: UserModel) => {

const addressIsSupported = pipe(user.address,
parseAddress,
_ => _.country,
getISOCountryCode,
['US', 'MX', 'CA'].includes
)


if (addressIsSupported) {
// submit order
}
}

💪 Some things you should know

  1. fp-ts is completely type-safe, so the return type of each function in a flow or pipe must match the parameter type of the next function.

2. Each function in aflow or pipe must accept exactly one parameter and return exactly one value. But don’t worry! In future posts, we’ll learn how to include more kinds of functions, including:

  • functions that take more than one parameter
  • side effects that don’t return a value, like console.log

😀 Hope that helped!

But if you have any doubts, leave a comment and I’ll get back to you :)

And hit that subscribe button to catch my next post about functional programming in TypeScript.

I’m Liam, the co-founder of Candle Finance.

We’re a Techstars-backed startup on a mission to make DIY investing safer and more automatic, so you can invest in what you believe in—and then go and live your life.

Sign up for our beta here:

--

--