Maybe / Option
Maybe/Option은 값이 있을 수도 있고 없을 수도 있다는 사실을 타입으로 드러내는 합 타입입니다. 값이 있으면 Some이나 Just, 값이 없으면 None이나 Nothing처럼 두 경우만 허용합니다.
중요한 점은 '없음'을 숨겨진 예외나 어중간한 null로 흘리지 않고, 정상적으로 다뤄야 할 값의 한 형태로 끌어올린다는 것입니다. 그래서 호출자는 이 값이 비어 있을 가능성을 코드에서 바로 읽게 됩니다.
▶아키텍처 다이어그램
📊 데이터 흐름 다이어그램점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다
함수 시그니처에는 string을 돌려준다고 써 있는데 실제로는 null도 섞여 오면, 호출자는 문서를 다시 읽거나 런타임에서 깨져 보고 나서야 부재 가능성을 알게 됩니다. 체크를 한 번만 빼먹어도 오류는 가장 늦고 귀찮은 순간에 터집니다. 문제는 '값이 없을 수도 있다'는 사실이 타입에 정직하게 드러나지 않기 때문입니다. 부재가 시스템에서 충분히 정상적인 상태라면, 그 가능성도 반환값 안으로 끌어와 드러내는 편이 훨씬 낫습니다.
Tony Hoare가 null reference를 '10억 달러짜리 실수'라고 부른 이후, 함수형 언어들은 부재 가능성을 값으로 모델링하는 방향을 강하게 밀었습니다. Haskell의 Maybe, ML 계열의 Option, Rust의 Option이 그 대표 예입니다. 이 흐름은 순수 함수와도 잘 맞습니다. 실패나 부재를 예외로 바깥으로 던지는 대신 반환값 안에 담아 두면, 함수는 여전히 입력에서 출력으로 이어지는 계산으로 읽힙니다. 오늘날 JavaScript나 TypeScript에서도 nullable 값의 위험이 반복되면서, Option 스타일 모델링을 라이브러리와 도메인 모델 수준에서 도입하는 사례가 많아졌습니다.
Maybe/Option의 내부 구조는 매우 단순합니다. 하나의 값이 두 가지 변형 중 하나만 가질 수 있습니다. Some(value)는 실제 값이 존재하는 경우이고, None은 값이 비어 있는 경우입니다. 이 타입을 받는 쪽은 보통 세 가지 방식으로 소비합니다. 첫째, 패턴 매칭이나 switch로 두 경우를 모두 분기합니다. 둘째, 값이 있는 경우에만 map으로 다음 계산을 적용합니다. 셋째, 값이 없을 때 쓸 기본값을 unwrapOr 같은 연산으로 제공합니다. 중요한 점은 어떤 방식을 택하든 '비어 있음'을 코드가 명시적으로 다뤄야 한다는 것입니다.
nullable 값을 Option으로 끌어올리기
type Option<T> =
| { kind: "some"; value: T }
| { kind: "none" };
function findUserNickname(id: string): Option<string> {
const nickname = db.users[id]?.nickname;
if (nickname == null) {
return { kind: "none" };
}
return { kind: "some", value: nickname };
}함수 시그니처가 값의 부재 가능성을 숨기지 않습니다. `null`을 그대로 흘리는 대신 `some` 또는 `none` 두 경우 중 하나로 돌려줍니다.
값이 있을 때만 다음 계산을 적용
type Option<T> =
| { kind: "some"; value: T }
| { kind: "none" };
function map<T, U>(
option: Option<T>,
transform: (value: T) => U
): Option<U> {
return option.kind === "some"
? { kind: "some", value: transform(option.value) }
: { kind: "none" };
}
function unwrapOr<T>(option: Option<T>, fallback: T): T {
return option.kind === "some" ? option.value : fallback;
}
const upper = map(findUserNickname("42"), (nickname) => nickname.toUpperCase());
const label = unwrapOr(upper, "GUEST");
// 값이 있으면 대문자로 바뀌고, 없으면 기본값을 사용한다.`some`일 때만 다음 계산이 실행되고 `none`이면 흐름이 그대로 유지됩니다. 마지막에 `unwrapOr`로 기본값을 주면 부재 처리를 한곳에 모을 수 있습니다.
Maybe/Option과 Either/Result는 둘 다 예외를 값으로 끌어올리지만 전달하는 정보량이 다릅니다. Maybe/Option은 '있다/없다'만 표현합니다. 왜 없는지는 담지 않습니다. 반면 Either/Result는 실패 이유까지 함께 실어 나릅니다. 단순 조회 실패나 선택 필드처럼 부재 자체만 중요하면 Maybe/Option이 맞고, 검증 오류처럼 실패 원인을 호출자에게 설명해야 하면 Either/Result가 더 적합합니다.
nullable 타입과도 구분해야 합니다. string | null은 부재 가능성을 타입으로 드러내긴 하지만, 대부분의 언어에서 null은 다른 연산과 쉽게 섞이고 별도 연산자 체계가 약합니다. Maybe/Option은 두 경우를 다루는 전용 API와 패턴 매칭을 중심에 두므로, 부재 처리를 더 구조적으로 강제합니다.
Maybe/Option은 '값이 없는 것이 이상한 실패가 아니라 자연스러운 상태'인 모델에서 특히 잘 맞습니다. 검색 결과가 없을 수 있을 때, 선택 입력이 비어 있을 수 있을 때, 아직 설정되지 않은 값을 표현할 때가 대표적입니다. 실무에서는 모든 API 응답을 무조건 Option으로 감싸기보다는, 정말로 부재 자체가 핵심 의미인 자리에만 쓰는 편이 좋습니다. 너무 넓게 쓰면 오히려 온갖 단계에서 기본값 처리만 늘어나 모델이 흐려질 수 있기 때문입니다.