Hone logo
Hone
Problems

Implement a Robust Option/Maybe Type in TypeScript

Functional programming languages often utilize "Option" or "Maybe" types to gracefully handle the presence or absence of a value. This challenge asks you to implement such a type in TypeScript, providing a safe and expressive way to deal with potentially null or undefined values in your code.

Problem Description

You are tasked with creating a generic Option<T> type in TypeScript. This type will represent a value that may or may not be present. It should have two states: Some<T> for when a value exists, and None for when no value is present.

Your implementation should include:

  • A Union Type: Define a union type that can either be Some<T> or None.
  • Some<T> Representation: A way to represent the presence of a value of type T.
  • None Representation: A way to represent the absence of a value.
  • Core Methods: Implement the following methods on the Option<T> type:
    • isSome(): boolean: Returns true if the Option contains a value, false otherwise.
    • isNone(): boolean: Returns true if the Option does not contain a value, false otherwise.
    • unwrap(): T: Returns the contained value. Throws an error if the Option is None.
    • unwrapOr(defaultValue: T): T: Returns the contained value if Some, otherwise returns defaultValue.
    • map<U>(fn: (value: T) => U): Option<U>: If Some, applies the function fn to the contained value and returns a new Option with the result. If None, returns None.
    • flatMap<U>(fn: (value: T) => Option<U>): Option<U>: If Some, applies the function fn (which itself returns an Option) to the contained value and returns the result. If None, returns None.
    • getOrElse(defaultValue: T): T: Similar to unwrapOr, but often used when defaultValue might be an expensive computation or another Option. (For this challenge, unwrapOr is sufficient, but consider the conceptual difference.)

Edge Cases to Consider:

  • Handling null and undefined as inputs when creating an Option.
  • Ensuring unwrap() throws an appropriate error when called on None.
  • The behavior of map and flatMap when the provided function fn returns null or undefined (they should result in None).

Examples

Example 1: Creating and Checking Options

// Assume Option<T> and related types/functions are defined
const someNumber: Option<number> = Some(10);
const noNumber: Option<number> = None;

console.log(someNumber.isSome()); // Output: true
console.log(someNumber.isNone()); // Output: false
console.log(noNumber.isSome());   // Output: false
console.log(noNumber.isNone());   // Output: true

Example 2: Unwrapping Values

const someValue: Option<string> = Some("hello");
const emptyValue: Option<string> = None;

console.log(someValue.unwrap());        // Output: "hello"
console.log(someValue.unwrapOr("default")); // Output: "hello"
console.log(emptyValue.unwrapOr("default")); // Output: "default"

try {
  emptyValue.unwrap(); // This will throw an error
} catch (e: any) {
  console.log(e.message); // Output: "Cannot unwrap None"
}

Example 3: Using map

const numberOption: Option<number> = Some(5);
const stringOption: Option<string> = None;

const squaredOption = numberOption.map(x => x * x); // Some(25)
const mappedStringOption = stringOption.map(s => s.toUpperCase()); // None

console.log(squaredOption.unwrap()); // Output: 25
console.log(mappedStringOption.isNone()); // Output: true

const nullReturningMap = Some(10).map(x => {
    if (x > 5) {
        return null; // Should result in None
    }
    return x * 2;
});
console.log(nullReturningMap.isNone()); // Output: true

Example 4: Using flatMap

function safeDivide(numerator: number, denominator: number): Option<number> {
    if (denominator === 0) {
        return None;
    }
    return Some(numerator / denominator);
}

const initialOption: Option<number> = Some(20);

const result1 = initialOption.flatMap(x => safeDivide(x, 4)); // Some(5)
const result2 = initialOption.flatMap(x => safeDivide(x, 0)); // None
const result3 = None.flatMap(x => safeDivide(x, 2));         // None

console.log(result1.unwrap()); // Output: 5
console.log(result2.isNone()); // Output: true
console.log(result3.isNone()); // Output: true

Constraints

  • The implementation must be in TypeScript.
  • The Option<T> type should be generic.
  • All methods must be implemented correctly according to their descriptions.
  • The solution should be performant and avoid unnecessary overhead.

Notes

  • Consider how you will represent Some and None. A discriminated union is a common and effective approach in TypeScript.
  • Think about factory functions (e.g., Some(value)) to easily create Option instances.
  • The unwrap method is intentionally unsafe. In real-world scenarios, you would typically prefer unwrapOr, map, or flatMap for safer handling of None values.
  • You do not need to implement the getOrElse method for this challenge, but understanding its purpose is beneficial.
Loading editor...
typescript