FP-TS Functional Programming in TypeScript

Exploring FP-TS Options in TypeScript

Nicholas Hammond portrait

Nicholas Hammond

24 December 2021 · 5 min read

This article is part of a series aimed at helping TypeScript users unfamiliar with functional programming approach it by examining the fp-ts library.

Someone new to functional programming who read the previous article on pipe, flow and currying might wonder how pipelines are useful with real life scenarios. For example what happens if we have to deal with something which sometimes returns no value? To address this problem we will introduce the Option monad.

To make this as easy to digest as possible, we will avoid any subsequent mention of the dreaded monads, and simply describe the Option in a practical way. In fact you can just think of Option as a module. The Option module defines its own type, which is unsurprisingly named Option.

Discriminated Unions in TypeScript

If you are reading this article it probably means you are familiar with how types can be built from other types in TypeScript, in particular using object types and with the operators for unions |.

For example, from a type A we can create a new object type objectA which takes the form:

{
    _tag: 'A',
    value: valueOfTypeA
}

I can create another type of an analogous form from type B called objectB:

{
    _tag: 'B',
    value: valueOfTypeB
}

I could then define a new type from these two C = objectA | objectB. This means that an element of type C will be of either type objectA or objectB.

Our new type C is a demonstration of what is called a discriminated union. The "union" part of the term should be fairly obvious, given that it is a union of two types. The "discriminated" part of the term has to do with the _tag property. An element of type objectA can conceptually be thought as an element of type A which has been tagged.

In TypeScript, discriminated unions can be implemented as unions tagged with a string field, and they are needed to implement the Option type. Read more about discriminated unions in the TypeScript documentation.

Defining Options

Defining the Option type

The Option module can be imported directly

import * as O from "fp-ts/lib/Option";

As mentioned we can import the Option type from this module. If we were to define it ourselves it would look like:

type Option<A> = None | Some<A>

This means that an element of type Option will either be of type Some<A> which looks like:

{ _tag: 'Some', value: someValueOfTypeA }

or of type None which looks like:

{ _tag: 'None' }

This looks a lot like a discriminated union except one of the types is void. The Some<A> value can be thought of a value of type A tagged with Some, while None is just the tag None.

We can already get a sense of how this will be useful. Our first pass of our code might follow a naive approach which while in a big picture sense does what we want, in practice breaks down because some part of the process might not return a value. This would be a problem, given we're advocating functional programming where one wants continuity of input and output through a series of functions.

The Option module remedies this problem by taking those situations where a return type is void and turning it into an actual element of type None. Otherwise when we do get a value, say of type A, that value will be tagged with Some and returned, or to be more accurate an element of type Some<A> with the value inside will be returned.

Pushing a value into Option world

We can only take advantage of this Option type if we have a way to somehow translate the values and methods in our naive code into an 'option-ized' version. Thankfully the Option module provides what we need.

First things first, we'll see how we can take elements of some type and bring them into the world of Option.

The first one is of

declare const of: <A>(a: A) => Option<A>

of takes in a value and returns an option in a natural way

O.of(aValueOfTypeA)           // returns { _tag: 'Some', value: aValueOfTypeA }

There is another method fromNullable which has the same effect provided the value being entered isn't null or undefined.

O.fromNullable(aValueOfTypeA)   // returns { _tag: 'Some', value: aValueOfTypeA }

However, if it is of a None type, the results are different

O.of(undefined)           // returns { _tag: 'Some', value: undefined }
O.fromNullable(undefined)   // returns { _tag: 'None' }

To generate a None Option one can simply use none, for example:

O.none     // returns { _tag: 'None' }

How to move around in Option world

There is nothing stopping one from using repeated applications of the some function creating a kind of "nested" Option.

For example:

const someOption1 = O.some(1)            // { _tag: 'Some', value: 1 }
const someOption2 = O.some(someOption1)  // { _tag: 'Some', value: { _tag: 'Some', value: 1 }}

This isn't particularly useful. If we wanted to get back down to a "lower level" we can use flatten, like so:

const someOption2 = { _tag: 'Some', value: { _tag: 'Some', value: 1 } }

O.flatten(someOption) // returns { _tag: 'Some', value: 1 }

But it's not enough just to be able to turn the values we have into options. The functions found in our original code don't know about options so they are useless here. Worse yet there is still the issue that these functions might possibly not even give a value at all. Thankfully the Option module gives us a way to "lift up" a function which so that we can deal with this problem.

map is what is known as a higher order function, for the reason that it takes a function/s as an argument, and returns a function. It takes in a function of type (arg: A): B and spits out a new function of type (arg: Option<A>): Option<B>.

Let's give a concrete example:

We use the inbuilt fp-ts tool, fromNullable to create an item of type Option<string>:

const optOfStr: O.Option<string> = O.some("asd") // { _tag: 'Some', value: 'asd' }

We define a function which takes in an item of type string and returns a value of type number:

const strLenPlus1:((str: string) => number) = ((str: string) => (str.length + 1))
strLenPlus1("asd")   // returns 4

We "Option-ize" this new function with map, creating a function which takes in an item of type Option<string> and returns a value of type Option<number>:

const optStrLenPlus1:((str: O.Option<string>) => O.Option<number>) 
= O.map(strLenPlus1)

optStrLenPlus1(optOfStr)    // returns { _tag: 'Some', value: 4 }

chain is another tool provided by fp-ts to create new versions of functions but which is more powerful than map. It is also a higher order function, but the input is a function of the form (arg: A): Option<B> and returns a function of the form (arg: Option<A>): Option<B>.

Now lets give a concrete example of chain :

If we start with a function that takes in a string and returns an Option<number>

const stringLengthOption = (str: string) => O.some(str.length)
stringLengthOption("asdf")   // { _tag: 'Some', value: 4 }

We can now make a new function which takes in an Option<string> and returns Option<number>

const stringOptionToNumberOption =  O.chain(stringLengthOption)
const someStringOption = O.some("asdf")   // { _tag: 'Some', value: 'asdf' }
stringOptionToNumberOption(someStringOption)   // { _tag: 'Some', value: 4 }

Conclusion

So even in the case of where we aren't guaranteed values of a non-nullable type or even any value at all, we don't have to throw in the functional programming towel. The fp-ts library gives us tools to enhance what TypeScript already gives us so that we don't have to break from the function to function pattern of our program. In future articles we'll see how the Option module is just an introduction to the way fp-ts helps us avoid these kind of problems.

References

Nicholas Hammond portrait

WRITTEN BY

Nicholas Hammond

Hi, I’m Nicholas! I'm an analytically and creatively minded software developer who is always learning new technology. I've worked with TypeScript, React, GraphQL, Elixir, Ruby on Rails, HTML, and CSS. I have a deep love of pure mathematics and digital animation.

More articles

Flight Simulator built in Phoenix LiveView

Elixir Development

Building a Flight Simulator in Phoenix LiveView

Josh Price Portrait

Josh Price

3 April 2022 – 6 min read

TypeScript Logo

TypeScript

Why we use TypeScript vs JavaScript

Josh Price Portrait

Josh Price

24 November 2021 – 5 min read

Want to read more?

The latest news, articles, and resources, sent to your inbox occasionally.