Skip to main content

Nested optional values

Managing optionality with undefined and null can lead to tedious code, especially when dealing with default values.

Let's assume that we have the following values in scope:

declare var input: string | undefined;
declare function parseInput(input: string): Array<string>;
declare function transform(input: Array<string>): Array<string> | undefined;
declare function print(input: Array<string>): string;
declare function prettify(input: string): string;

Here, parse always returns an Array<string>, and transform can return either an Array<string> or undefined.

Handling this using null or undefined values would lead to code like the following:

const parsed = input != undefined ? parseInput(input) : undefined;
// Keep the `parsed` value if `transform` doesn't output
const transformed =
parsed != undefined ? transform(parsed) ?? parsed : undefined;
// Fallback at the end
const printed = transformed != undefined ? print(transformed) : undefined;
const value = printed != undefined ? prettify(printed) : "fallback";

We lose a lot of the code intent, as we're distracted with some unnecessary complexity.

Now, let's tweak our values so that we use the Option type instead of undefined:

declare var input: Option<string>;
declare function parseInput(input: string): Array<string>;
declare function transform(input: Array<string>): Option<Array<string>>;
declare function print(input: Array<string>): string;
declare function prettify(input: string): string;

Using Option, the same code as above can be written as follows:

input
.map(parseInput)
.flatMap(transform)
.map(print)
.map(prettify)
.getWithDefault("fallback");

Here, the intent of the code is clearly represented, making it much easier to follow.

If we need quick interop with existing code returning undefined or null values, Boxed provides transformers:

If we were to assume again that we have:

declare var input: string | undefined;
declare function parseInput(input: string): Array<string>;
declare function transform(input: Array<string>): Array<string> | undefined;
declare function print(input: Array<string>): string;
declare function prettify(input: string): string;

We'd simplfy need to write the following:

Option.fromNullable(input)
.map(parseInput)
.flatMap((input) => Option.fromNullable(transform(input)))
.map(print)
.map(prettify)
.getWithDefault("fallback");