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>orNone. Some<T>Representation: A way to represent the presence of a value of typeT.NoneRepresentation: A way to represent the absence of a value.- Core Methods: Implement the following methods on the
Option<T>type:isSome(): boolean: Returnstrueif theOptioncontains a value,falseotherwise.isNone(): boolean: Returnstrueif theOptiondoes not contain a value,falseotherwise.unwrap(): T: Returns the contained value. Throws an error if theOptionisNone.unwrapOr(defaultValue: T): T: Returns the contained value ifSome, otherwise returnsdefaultValue.map<U>(fn: (value: T) => U): Option<U>: IfSome, applies the functionfnto the contained value and returns a newOptionwith the result. IfNone, returnsNone.flatMap<U>(fn: (value: T) => Option<U>): Option<U>: IfSome, applies the functionfn(which itself returns anOption) to the contained value and returns the result. IfNone, returnsNone.getOrElse(defaultValue: T): T: Similar tounwrapOr, but often used whendefaultValuemight be an expensive computation or anotherOption. (For this challenge,unwrapOris sufficient, but consider the conceptual difference.)
Edge Cases to Consider:
- Handling
nullandundefinedas inputs when creating anOption. - Ensuring
unwrap()throws an appropriate error when called onNone. - The behavior of
mapandflatMapwhen the provided functionfnreturnsnullorundefined(they should result inNone).
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
SomeandNone. A discriminated union is a common and effective approach in TypeScript. - Think about factory functions (e.g.,
Some(value)) to easily createOptioninstances. - The
unwrapmethod is intentionally unsafe. In real-world scenarios, you would typically preferunwrapOr,map, orflatMapfor safer handling ofNonevalues. - You do not need to implement the
getOrElsemethod for this challenge, but understanding its purpose is beneficial.