Skip to main content

The Option Type

The Option<T> type represents a value that may or may not exist, providing a type-safe alternative to null and undefined.

Core Structure

Option<T>

A discriminated union of Some<T> | None:

type Option<T> = Some<T> | None;

This forces explicit handling of absent values, preventing null pointer exceptions and making the code's intent clear.

Factory Functions

some(value)

Creates an Option containing a value.

function some<T>(value: T): Some<T>;
import { some } from "@hex-di/result";

const user = some({ id: 1, name: "Alice" });
user.isSome(); // true
user.value; // { id: 1, name: 'Alice' }

none()

Creates an empty Option.

function none(): None;
import { none } from "@hex-di/result";

const missing = none();
missing.isNone(); // true

Type Guard

isOption(value)

Runtime type guard for Option.

import { isOption } from "@hex-di/result";

if (isOption(maybeOption)) {
// maybeOption is Option<unknown>
if (maybeOption.isSome()) {
console.log(maybeOption.value);
}
}

Option Methods

MethodSomeNone
isSome()truefalse
isNone()falsetrue
map(f)some(f(value))none()
andThen(f)f(value)none()
unwrapOr(default)valuedefault
match(onSome, onNone)onSome(value)onNone()

Example Usage

import { some, none, type Option } from "@hex-di/result";

function findUser(id: string): Option<User> {
const user = database.get(id);
return user ? some(user) : none();
}

const maybeUser = findUser("123");

// Pattern matching
const greeting = maybeUser.match(
user => `Hello, ${user.name}!`,
() => "User not found"
);

// Transformation
const maybeName = maybeUser.map(user => user.name).map(name => name.toUpperCase());

// Chaining
const maybeProfile = maybeUser.andThen(user => findProfile(user.id));

// Extracting with default
const userName = maybeUser.map(user => user.name).unwrapOr("Anonymous");

Serialization

fromOptionJSON(json)

Deserializes an OptionJSON back to Option<T>.

import { fromOptionJSON } from "@hex-di/result";

const someJson = { _tag: "Some", value: 42 };
const option = fromOptionJSON(someJson); // some(42)

const noneJson = { _tag: "None" };
const empty = fromOptionJSON(noneJson); // none()

When to Use Option vs Nullable

Use Option when:

  • Explicit absence handling is important — The type system should enforce handling of missing values
  • Chaining operations — You need to chain multiple operations that may produce absent values
  • API boundaries — Public APIs benefit from explicit Option types
  • Domain modeling — When absence has specific meaning in your domain

Use nullable (T | null | undefined) when:

  • JavaScript interop — Working with existing JavaScript APIs
  • Simple checks — The logic is straightforward and doesn't benefit from Option methods
  • Performance critical paths — Option adds a small overhead
  • Framework conventions — Your framework expects nullable values

Example: Option in Practice

import { some, none, type Option, fromNullable, ok, err } from "@hex-di/result";

interface UserPreferences {
theme?: string;
language?: string;
notifications?: boolean;
}

class PreferencesService {
getTheme(userId: string): Option<string> {
const prefs = this.loadPreferences(userId);
return prefs.theme ? some(prefs.theme) : none();
}

setTheme(userId: string, theme: string): Result<void, string> {
if (!this.isValidTheme(theme)) {
return err("Invalid theme");
}

this.savePreference(userId, "theme", theme);
return ok(undefined);
}

getEffectiveTheme(userId: string): string {
return this.getTheme(userId).unwrapOr("light"); // Default to light theme
}

// Convert from nullable API
getLanguageFromAPI(userId: string): Option<string> {
const apiResponse = this.api.getUserLanguage(userId); // Returns string | null
return apiResponse ? some(apiResponse) : none();
}
}

Combining with Result

Option and Result work together seamlessly:

import { ok, err, some, none, type Result, type Option } from "@hex-di/result";

// Convert Option to Result
function requireUser(maybeUser: Option<User>): Result<User, string> {
return maybeUser.match(
user => ok(user),
() => err("User not found")
);
}

// Result method returns Option
const result: Result<number, string> = ok(42);
const option: Option<number> = result.toOption(); // Some(42)

// Chain Option and Result operations
function processUser(id: string): Result<string, string> {
return findUser(id) // Returns Option<User>
.andThen(user => some(user.email)) // Extract email if user exists
.match(
email => ok(`Email: ${email}`),
() => err("No email found")
);
}