Skip to content

Functional Programming in TypeScript Using the fp-ts Library: Deep Dive Into Option's Methods and Other Useful fp-ts Operators

Welcome back to our blog series on functional programming with fp-ts! In our previous posts, we talked about the building block of fp-ts library: Pipe and Flow operators and we introduced one of the most useful types in the library: Option type. Let's start to use our knowledge and combine all the blocks: in this blog post, we'll take a deep dive into fp-ts' Option type, and explore its fundamental methods such as fold, fromNullable, and getOrElse. We'll then leverage the map, flatten, and chain operators, combining them with our powerful (and already known) operator to compose expressive and concise code.

Understanding Option

The Option type, also known as Maybe, represents values that might be absent. It is particularly useful for handling scenarios where a value could be missing, eliminating the need for explicit null checks. fp-ts equips us with a rich set of methods and operators to work with Option efficiently.

fold: The fold method allows us to transform an Option value into a different type by providing two functions: one for the None case, and another for the Some case. The pipe operator enhances the readability of the code by enabling a fluent and concise syntax.

import { Option, none, some } from 'fp-ts/lib/Option';
import { fold } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';

const value: Option<number> = some(10);

const result = pipe(
  value,
  fold(
    () => 'No value',
    (x: number) => `Value is ${x}`
  )
); // result: "Value is 10"

In this example, we have an Option value some(10), representing the presence of the number 10. We use the pipe operator from fp-ts to chain the value through the fold function, passing in two functions. The first function, () => 'No value', handles the None case when the Option is empty. The second function, (x: number) => Value is ${x}, handles the Some case and receives the value inside the Option (in this case, 10). The resulting value is "Value is 10".

fromNullable: The fromNullable function converts nullable values (e.g., null or undefined) into an Option. We can leverage pipe to make the code more readable and maintainable.

import { Option, fromNullable } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';

const value: string | null = 'Hello, world!';

const optionValue: Option<string> = pipe(value, fromNullable);

In the example, we have a string value 'Hello, world!', which is not nullable. However, by using the pipe operator and passing the value through fromNullable, fp-ts internally checks if the value is null or undefined. If it is, it produces a None value, indicating the absence of a value. Otherwise, it wraps the value inside Some. So, in this case, the resulting optionValue is Some("Hello, world!").

getOrElse: The getOrElse method allows us to extract the value from an Option or provide a default value if the Option is None. Pipe operator aids in composing the getOrElse function with other operations seamlessly.

import { Option, some, none } from 'fp-ts/lib/Option';
import { getOrElse } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';

const optionValue: Option<number> = some(10);

const value = pipe(optionValue, getOrElse(() => 0)); // value: 10

const noneValue: Option<number> = none;

const defaultValue = pipe(noneValue, getOrElse(() => 0)); // defaultValue: 0

In the first example, we have an Option value some(10). Using the pipe operator, and passing the Option through getOrElse, we provide a function () => 0 as a default value. Since the Option is Some(10), the function is not executed, and the resulting value is 10. In the second example, we have an Option value none, representing the absence of a value. Again, using the pipe operator and getOrElse, we provide a default value of 0. Since the Option is None, the function () => 0 is executed, resulting in the default value of 0.

Map, Flatten, and Chain Operators

Building upon the foundational methods of Option, fp-ts provides powerful operators like map, flatten, and chain, which enable developers to compose complex operations in a functional and expressive manner.

map: The map operator allows us to transform the value inside an Option using a provided function. It applies the function only if the Option is Some.

import { Option, some } from 'fp-ts/lib/Option';
import { map } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';

const optionValue: Option<number> = some(10);

const mappedValue: Option<string> = pipe(optionValue, map((x: number) => `Value is ${x}`)); // mappedValue: Some("Value is 10")

In this example, we have an Option value some(10). Using the pipe operator and passing the Option through map, we provide a function (x: number) => Value is ${x}. Since the Option is Some(10), the function is applied to the value inside the Option, resulting in a new Option Some("Value is 10").

flatten: The flatten operator allows us to flatten nested Options into a single Option. It simplifies the resulting structure when we have computations that may produce an Option inside another Option. The pipe operator assists in composing flatten operations seamlessly.

import { Option, some, none } from 'fp-ts/lib/Option';
import { flatten } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';

const nestedOption: Option<Option<number>> = some(some(10));

const flattenedOption: Option<number> = pipe(nestedOption, flatten); // flattenedOption: Some(10)

In the example, we have a nested Option some(some(10)). Using the pipe operator and passing the nested Option through flatten, fp-ts flattens the structure, resulting in a single Option Some(10).

chain: The chain operator, also known as flatMap or >>=, combines the functionalities of map and flatten. It allows us to apply a function that produces an Option to the value inside an Option, resulting in a flattened Option.

import { Option, some, none } from 'fp-ts/lib/Option';
import { chain } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';

const optionValue: Option<number> = some(42);

const chainedValue: Option<string> = pipe(
  optionValue,
  chain((x: number) => (x > 10 ? some(`Value is ${x}`) : none))
);
// chainedValue: Some("Value is 42")

const noneValue: Option<number> = none;

const noneChainedValue: Option<string> = pipe(
  noneValue,
  chain((x: number) => (x > 10 ? some(`Value is ${x}`) : none))
);
// noneChainedValue: None

In the first example, we have an Option value some(42). Using the pipe operator and passing the Option through chain, we provide a function that checks if the value is greater than 10. If it is, it returns Some(Value is ${x}), where x is the value inside the Option. Since the value is 42, which is greater than 10, the resulting Option is Some("Value is 42"). In the second example, we have an Option value none, representing the absence of a value. When passing it through chain with the same function as before, the function is not executed because the Option is None, resulting in None.

Conclusion

fp-ts provides powerful methods and operators for working with the Option type, allowing developers to embrace functional programming principles effectively. By understanding the fold, fromNullable, and getOrElse methods, as well as the map, flatten, and chain operators, and combining them with the pipe operator, developers can write expressive, maintainable, and resilient code. Explore these tools, unlock their potential, and take your functional programming skills to the next level!