Skip to content

Latest commit

 

History

History
101 lines (79 loc) · 3.05 KB

File metadata and controls

101 lines (79 loc) · 3.05 KB
title Model Optional Values Safely with Option
id model-optional-values-with-option
skillLevel intermediate
applicationPatternId domain-modeling
summary Use Option<A> to explicitly represent a value that may or may not exist, eliminating null and undefined errors.
tags
option
optional-values
null-handling
domain-modeling
type-safety
data
rule
description
Use Option<A> to explicitly model values that may be absent, avoiding null or undefined.
related
model-validated-domain-types-with-brand
author effect_website
lessonOrder 6

Guideline

Represent values that may be absent with Option<A>. Use Option.some(value) to represent a present value and Option.none() for an absent one. This creates a container that forces you to handle both possibilities.


Rationale

Functions that can return a value or null/undefined are a primary source of runtime errors in TypeScript (Cannot read properties of null).

The Option type solves this by making the possibility of an absent value explicit in the type system. A function that returns Option<User> cannot be mistaken for a function that returns User. The compiler forces you to handle the None case before you can access the value inside a Some, eliminating an entire class of bugs.


Good Example

A function that looks for a user in a database is a classic use case. It might find a user, or it might not. Returning an Option<User> makes this contract explicit and safe.

import { Effect, Option } from "effect";

interface User {
  id: number;
  name: string;
}

const users: User[] = [
  { id: 1, name: "Paul" },
  { id: 2, name: "Alex" },
];

// This function safely returns an Option, not a User or null.
const findUserById = (id: number): Option.Option<User> => {
  const user = users.find((u) => u.id === id);
  return Option.fromNullable(user); // A useful helper for existing APIs
};

// The caller MUST handle both cases.
const greeting = (id: number): string =>
  findUserById(id).pipe(
    Option.match({
      onNone: () => "User not found.",
      onSome: (user) => `Welcome, ${user.name}!`,
    })
  );

const program = Effect.gen(function* () {
  yield* Effect.log(greeting(1)); // "Welcome, Paul!"
  yield* Effect.log(greeting(3)); // "User not found."
});

Effect.runPromise(program);

Anti-Pattern

The anti-pattern is returning a nullable type (e.g., User | null or User | undefined). This relies on the discipline of every single caller to perform a null check. Forgetting even one check can introduce a runtime error.

interface User {
  id: number;
  name: string;
}
const users: User[] = [{ id: 1, name: "Paul" }];

// ❌ WRONG: This function's return type is less safe.
const findUserUnsafely = (id: number): User | undefined => {
  return users.find((u) => u.id === id);
};

const user = findUserUnsafely(3);

// This will throw "TypeError: Cannot read properties of undefined (reading 'name')"
// because the caller forgot to check if the user exists.
console.log(`User's name is ${user.name}`);