본문 바로가기

모나드와 옵셔널 체이닝

Maybe 형태로 값의 부재를 연쇄 처리하는 모나드적 구성을 이해하고 옵셔널 체이닝과 null 병합으로 안전하게 단순화합니다.

함수형 프로그래밍은 순수 함수와 불변성을 통해 예측 가능하고 안정적인 코드를 지향합니다.

하지만 실제 애플리케이션 개발에서는 null 또는 undefined 값 처리, 오류 관리, 비동기 작업 처리 등 복잡한 상황을 마주하게 됩니다.

이러한 복잡성을 함수형 방식으로 우아하게 다루기 위한 추상화 개념 중 하나가 바로 모나드(Monads)입니다.

모나드는 다소 추상적이고 이해하기 어려운 개념으로 알려져 있지만, 사실 우리는 이미 모나드와 유사한 패턴들을 일상적으로 사용하고 있습니다.

타입스크립트의 옵셔널 체이닝(Optional Chaining)은 모나드적 사고방식이 적용된 구문 설탕(Syntactic Sugar)의 좋은 예시입니다.


모나드 (Monads) 개념 이해

정의: 모나드는 특정 연산(함수)들을 순서대로 실행하면서 컨텍스트(Context)를 안전하게 전달하는 방법을 제공하는 디자인 패턴입니다.

이는 값을 컨텍스트(wrapper) 안에 넣고, 그 컨텍스트 안에서 값을 변환하는 일련의 연산을 정의하는 것입니다.

설명: 모나드는 함수형 프로그래밍에서 "어떤 타입 <T>에 TT 값을 가지고 있는 경우, 이 T 값을 인자로 받는 함수 f: T-> M<U>를 안전하게 적용하여 M<U>를 얻을 수 있게 해주는 패턴"이라고 설명할 수 있습니다.

여기서 M은 컨텍스트를 의미합니다.

가장 흔하게 접하는 모나드의 예시는 다음과 같습니다.

Promise: 비동기 컨텍스트에서 값을 다룹니다.

  • Promise<T>는 비동기적으로 얻게 될 값 T를 감싸고 있는 컨텍스트입니다.
  • Promise.resolve(value)는 값을 Promise 컨텍스트에 넣는 리프트(lift) 또는 unit 연산과 유사합니다.
  • .then() 메서드는 T를 U로 변환하는 함수 f: T -> Promise<U>를 받아 Promise<U>를 반환합니다. 이는 모나드의 핵심 연산인 flatMap (또는 bind)과 유사합니다.
// Promise는 비동기 모나드의 한 종류
asyncOperation<number>(10) // Promise<number> 컨텍스트
  .then(result => processResult(result)) // result(number)를 받아 Promise<string> 반환
  .then(finalResult => console.log(finalResult)); // 마지막 console.log 콜백의 반환형에 따라 Promise<void>

Array: 컬렉션 컨텍스트에서 값을 다룹니다.

  • Array<T>는 여러 개의 값 T를 담고 있는 컨텍스트입니다.
  • map 메서드는 T를 U로 변환하는 함수 f: T -> U를 받아 Array<U>를 반환합니다.
  • flatMap (또는 map 후 flat)은 T를 Array<U>로 변환하는 함수 f: T -> Array<U>를 받아 Array<U>를 반환합니다.
const numbers = [1, 2, 3]; // Array<number> 컨텍스트
numbers.flatMap(num => [num, num * 2]); // num(number)를 받아 number[] 반환 -> Array<number>
// 결과: [1, 2, 2, 4, 3, 6]

모나드를 이해하는 가장 중요한 열쇠는 컨텍스트를 유지하면서 연산을 연결(chain)하는 능력입니다.

모나드는 함수형 프로그래밍에서 이러한 컨텍스트의 흐름을 안전하게 제어하고, 오류 처리나 비동기 처리와 같은 복잡한 부수 효과를 순수 함수형 방식으로 캡슐화하는 데 사용됩니다.

Maybe/Optional 모나드 (Null 안전성)

Option은 부재 문맥을 파이프라인 끝까지 보존한다 — 상태 추적

Some/None에서 map/flatMap 콜백 실행 또는 건너뜀과 최종 추출이 달라진다. 본문 Maybe 구현과 도표 Option 일반 모델을 구별해 확장 사례임을 명시해야 한다.

fromNullable에서 Some/None으로 분기하고 Some의 map/flatMap 또는 None의 콜백 생략 뒤 최종 match로 접는다. 교재 Maybe API와 태그 설계를 구별한다.

OPTION / PRESERVE ABSENCE

Option은 값 변환 중 생기는 부재도 전달한다

태그 기반 Option은 nullable 값을 Some/None으로 표현합니다. Some에만 변환을 적용하고 None은 건너뜁니다. flatMap의 콜백도 None을 돌려줄 수 있으므로 최종 결과에서 두 경우를 처리합니다.

Option은 값 변환 중 생기는 부재도 전달한다fromNullable에서 Some/None으로 분기하고 Some의 map/flatMap 또는 None의 콜백 생략 뒤 최종 match로 접는다. 교재 Maybe API와 태그 설계를 구별한다.nullable → fromNullableOption으로 감싸기Some현재 값이 있음None현재 값이 없음map / flatMap변환 중 부재도 가능콜백 건너뜀None 문맥 유지match / foldSome·None 최종 결과
Some·None 분기와 최종 결과 처리nullable 입력에서 Some과 None으로 갈린다. Some만 map/flatMap으로 변환하며 변환 결과도 부재일 수 있다. None은 콜백을 생략한다. 두 경로는 최종 match/fold에서 Some·None을 처리한다. 세로 배치한 Some과 None 사이의 간선은 없고 교재의 간단한 Maybe는 태그·match API를 제공하지 않는다.nullablefromNullableOption으로 감싸기Some현재 값이 있음None현재 값이 없음map / flatMap변환 중 부재도 가능콜백 건너뜀None 문맥 유지match / foldSome·None 최종 결과

MAP

값을 변환하고 다시 감싼다

태그 Option의 map은 T→U 함수를 적용해 Option<U>로 감쌉니다. Option 반환 함수를 map하면 중첩 문맥이 되므로 연결 방식이 달라집니다.

FLAT MAP

이미 감싼 결과를 연결

T→Option<U> 콜백 결과를 바로 연결합니다. Some 입력도 콜백이 None을 반환하면 이후 부재 경로를 따릅니다.

LESSON API

원문 Maybe는 간단한 별도 구현

원문은 of·map·flatMap·getOrElse를 사용하며 Some/None 태그나 match를 제공하지 않습니다. 원문 map은 nullish 반환도 부재로 다룹니다.

ACTUAL VALUES

Bob은 값이 있는 경로

user2.name은 Bob이므로 결과는 BOB입니다. 이름 없는 user3이면 기본 이름 경로를 설명할 수 있습니다. 함수의 throw를 이 간단한 Maybe가 자동으로 잡지는 않습니다.

태그 Option 구조와 간단한 Maybe 예제의 API를 구분합니다. 부재를 숨기거나 모든 오류를 자동 처리하는 추상화로 해석하지 않습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
  • 원래 본문의 구현과 도표가 설명한 설계 보강을 분리한다. 서사에 포함한 확장 동작은 assumptions에 명시하고 이미 구현된 안전장치로 주장하지 않는다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
입력 문맥입력: 값 또는 nullish / 문맥: Some/None 모델fromNullable은 존재 값 또는 없음 문맥을 만든다본문 Maybe API와 그림 Option 일반 모델은 별개 확장이다
callback 적용Some: 변환 결과 / None: callback 미실행존재하면 map/flatMap을 실행하고 없으면 건너뛴다없음을 보통 값처럼 callback에 넘기지 않는다
경계에서 추출존재: 계산 값 / 없음: 명시한 대체 값match/fold 또는 본문 getOrElse 정책으로 UI 값을 고른다문맥 내부 변환과 최종 사용 정책을 구분한다

Option은 부재 문맥을 파이프라인 끝까지 보존한다 — 상태 추적

Some/None에서 map/flatMap 콜백 실행 또는 건너뜀과 최종 추출이 달라진다. 본문 Maybe 구현과 도표 Option 일반 모델을 구별해 확장 사례임을 명시해야 한다.

fromNullable에서 Some/None으로 분기하고 Some의 map/flatMap 또는 None의 콜백 생략 뒤 최종 match로 접는다. 교재 Maybe API와 태그 설계를 구별한다.

OPTION / PRESERVE ABSENCE

Option은 값 변환 중 생기는 부재도 전달한다

태그 기반 Option은 nullable 값을 Some/None으로 표현합니다. Some에만 변환을 적용하고 None은 건너뜁니다. flatMap의 콜백도 None을 돌려줄 수 있으므로 최종 결과에서 두 경우를 처리합니다.

Option은 값 변환 중 생기는 부재도 전달한다fromNullable에서 Some/None으로 분기하고 Some의 map/flatMap 또는 None의 콜백 생략 뒤 최종 match로 접는다. 교재 Maybe API와 태그 설계를 구별한다.nullable → fromNullableOption으로 감싸기Some현재 값이 있음None현재 값이 없음map / flatMap변환 중 부재도 가능콜백 건너뜀None 문맥 유지match / foldSome·None 최종 결과
Some·None 분기와 최종 결과 처리nullable 입력에서 Some과 None으로 갈린다. Some만 map/flatMap으로 변환하며 변환 결과도 부재일 수 있다. None은 콜백을 생략한다. 두 경로는 최종 match/fold에서 Some·None을 처리한다. 세로 배치한 Some과 None 사이의 간선은 없고 교재의 간단한 Maybe는 태그·match API를 제공하지 않는다.nullablefromNullableOption으로 감싸기Some현재 값이 있음None현재 값이 없음map / flatMap변환 중 부재도 가능콜백 건너뜀None 문맥 유지match / foldSome·None 최종 결과

MAP

값을 변환하고 다시 감싼다

태그 Option의 map은 T→U 함수를 적용해 Option<U>로 감쌉니다. Option 반환 함수를 map하면 중첩 문맥이 되므로 연결 방식이 달라집니다.

FLAT MAP

이미 감싼 결과를 연결

T→Option<U> 콜백 결과를 바로 연결합니다. Some 입력도 콜백이 None을 반환하면 이후 부재 경로를 따릅니다.

LESSON API

원문 Maybe는 간단한 별도 구현

원문은 of·map·flatMap·getOrElse를 사용하며 Some/None 태그나 match를 제공하지 않습니다. 원문 map은 nullish 반환도 부재로 다룹니다.

ACTUAL VALUES

Bob은 값이 있는 경로

user2.name은 Bob이므로 결과는 BOB입니다. 이름 없는 user3이면 기본 이름 경로를 설명할 수 있습니다. 함수의 throw를 이 간단한 Maybe가 자동으로 잡지는 않습니다.

태그 Option 구조와 간단한 Maybe 예제의 API를 구분합니다. 부재를 숨기거나 모든 오류를 자동 처리하는 추상화로 해석하지 않습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
  • 원래 본문의 구현과 도표가 설명한 설계 보강을 분리한다. 서사에 포함한 확장 동작은 assumptions에 명시하고 이미 구현된 안전장치로 주장하지 않는다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
입력 문맥입력: 값 또는 nullish / 문맥: Some/None 모델fromNullable은 존재 값 또는 없음 문맥을 만든다본문 Maybe API와 그림 Option 일반 모델은 별개 확장이다
callback 적용Some: 변환 결과 / None: callback 미실행존재하면 map/flatMap을 실행하고 없으면 건너뛴다없음을 보통 값처럼 callback에 넘기지 않는다
경계에서 추출존재: 계산 값 / 없음: 명시한 대체 값match/fold 또는 본문 getOrElse 정책으로 UI 값을 고른다문맥 내부 변환과 최종 사용 정책을 구분한다

그림의 fromNullable·Some/None·match는 태그 기반 Option의 일반적인 설계입니다. 아래 간단한 Maybe 클래스는 그 대신 of·내부 nullish 값·getOrElse를 사용하며 match API를 제공하지 않습니다.

가장 쉽게 이해할 수 있는 모나드 중 하나는 null 또는 undefined 값을 안전하게 다루는 Maybe 또는 Optional 모나드입니다.

이는 null 체크를 덕지덕지 붙이는 대신, 값을 컨텍스트 안에 가둬두고, 컨텍스트 내부에서만 연산이 일어나도록 합니다.

// Maybe 모나드 (간단한 예시 구현)
class Maybe<T> {
  private value: T | null | undefined;

  private constructor(value: T | null | undefined) {
    this.value = value;
  }

  // 값을 Maybe 컨텍스트에 넣는 팩토리 메서드
  static of<U>(value: U | null | undefined): Maybe<U> {
    return new Maybe(value);
  }

  // 핵심 연산: map (컨텍스트 안의 값을 변환)
  map<U>(fn: (value: T) => U): Maybe<U> {
    if (this.value === null || this.value === undefined) {
      return Maybe.of(null); // 값이 없으면 null 컨텍스트 유지
    }
    return Maybe.of(fn(this.value)); // 값이 있으면 함수 적용 후 새 Maybe 생성
  }

  // flatMap (또는 bind): 컨텍스트 안의 값을 다른 Maybe 컨텍스트로 변환
  flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U> {
    if (this.value === null || this.value === undefined) {
      return Maybe.of(null);
    }
    return fn(this.value); // 함수 적용 결과가 이미 Maybe이므로 바로 반환
  }

  // nullish이면 기본값을, 값이 있으면 그 값을 반환
  getOrElse(defaultValue: T): T {
    return this.value === null || this.value === undefined ? defaultValue : this.value;
  }
}

// 사용 예시
interface User {
  id: number;
  name?: string;
  address?: {
    street?: string;
    city?: string;
  };
}

const user1: User = { id: 1, name: "Alice", address: { street: "123 Main St", city: "Anytown" } };
const user2: User = { id: 2, name: "Bob" };
const user3: User = { id: 3, address: { city: "Othertown" } }; // name 없음

// Maybe 모나드를 사용하여 안전하게 속성 접근
const userName1 = Maybe.of(user1)
  .flatMap(u => Maybe.of(u.name)) // name이 Maybe<string>을 반환
  .map(name => name.toUpperCase()) // name이 있으면 대문자로 변환
  .getOrElse("UNKNOWN");
console.log("User 1 name:", userName1); // ALICE

const userName2 = Maybe.of(user2)
  .flatMap(u => Maybe.of(u.name)) // u.name은 "Bob"이므로 Maybe<string> 반환
  .map(name => name.toUpperCase()) // name을 "BOB"로 변환
  .getOrElse("UNKNOWN");
console.log("User 2 name:", userName2); // BOB

const userCity3 = Maybe.of(user3)
  .flatMap(u => Maybe.of(u.address)) // address가 있으면 Maybe<Address>
  .flatMap(a => Maybe.of(a.city)) // city가 있으면 Maybe<string>
  .getOrElse("Unknown City");
console.log("User 3 city:", userCity3); // Othertown

strict 모드에서 이어 실행하기

원래 클래스의 두 부재 분기에서 Maybe.of(null)을 호출하면, 팩터리의 타입 인수가 null로 추론되어 메서드가 약속한 Maybe<U>와 맞지 않을 수 있습니다. 예제를 별도 파일에 복사할 때는 map과 flatMap의 부재 분기를 각각 다음처럼 바꾸세요. 값이 없는 상태는 그대로 두고, 이어질 값의 타입 U를 명시하는 보완입니다.

// map<U>의 부재 분기
if (this.value === null || this.value === undefined) {
  return Maybe.of<U>(null);
}
// flatMap<U>의 부재 분기도 동일하게 적용
if (this.value === null || this.value === undefined) {
  return Maybe.of<U>(null);
}

두 메서드의 나머지 부분과 사용 예제는 그대로 둡니다. 다음 독립 연습도 이 보완을 적용한 클래스와 함께 실행합니다. 이 클래스는 값의 부재를 이어 전달하는 최소 구현이며, 콜백이 던진 예외까지 처리하거나 모나드 법칙을 증명한 구현은 아닙니다.

예제의 user2.name은 "Bob"이므로 userName2는 BOB가 됩니다. 부재 경로는 이름이 없는 user3으로 설명할 수 있습니다. 앞의 Promise 예제도 마지막 콜백이 console.log만 반환하므로 마지막 체인은 Promise<void>입니다. 이는 코드에서 도출한 결과이며 실행 관측은 아닙니다.

map의 콜백은 일반 값을 반환하고 다시 Maybe로 감쌉니다. flatMap의 콜백은 이미 Maybe를 반환하므로 그 결과를 직접 이어 붙입니다. 같은 이름 변환에 map으로 Maybe.of(u.name)을 반환했다면 다음 값은 이름 문자열이 아니라 안쪽 Maybe 객체입니다. 문맥을 한 겹 더 만들고 싶은지, 그 문맥을 이어 붙이고 싶은지에 따라 두 연산을 구별합니다. 이 간단한 구현은 콜백이 던진 예외를 error 문맥으로 바꾸지 않습니다.

Maybe 모나드는 null 값의 전파를 캡슐화하여, 중간에 null이 발생하면 이후의 모든 연산을 건너뛰고 기본값을 반환하도록 합니다.

이는 중첩된 if (obj && obj.prop) 체크를 제거하여 코드를 깔끔하게 만듭니다.


옵셔널 체이닝 (?.)

정의: 옵셔널 체이닝은 ECMAScript 2020(ES2020)에서 도입된 문법입니다. ?. 바로 왼쪽 값이 null 또는 undefined이면 해당 속성 접근·함수 호출·인덱스 접근을 건너뛰고 undefined를 반환합니다.

설명: 옵셔널 체이닝은 부재이면 다음 접근을 생략한다는 점에서 앞의 Maybe 흐름과 비교할 수 있습니다. 다만 모나드 인터페이스나 법칙을 언어 문법으로 구현한 것은 아니며, ?.가 놓인 위치의 null·undefined 값만 검사합니다.

속성 접근 (?.), 메서드 호출 (?.()), 배열 인덱싱 (?.[])에 사용할 수 있습니다.

타입스크립트 적용 예시
interface UserData {
  id: number;
  name?: string;
  contact?: {
    email?: string;
    phone?: string;
  };
  getProfile?(): string; // 메서드도 옵셔널
  roles?: string[]; // 배열도 옵셔널
}

const user1: UserData = {
  id: 1,
  name: "Charlie",
  contact: {
    email: "[email protected]",
    phone: "111-222-3333"
  },
  getProfile: () => "Charlie's Profile"
};

const user2: UserData = {
  id: 2,
  name: "David"
  // contact 속성 없음
};

const user3: UserData = {
  id: 3,
  roles: ['admin', 'editor']
};

// 1. 옵셔널 속성 접근
const email1 = user1.contact?.email;
console.log("User 1 email:", email1); // [email protected]

const email2 = user2.contact?.email; // user2.contact가 undefined이므로 전체 표현식은 undefined
console.log("User 2 email:", email2); // undefined

const email3 = user3.contact?.email;
console.log("User 3 email:", email3); // undefined

// 2. 옵셔널 메서드 호출
const profile1 = user1.getProfile?.(); // user1.getProfile이 존재하면 호출
console.log("User 1 profile:", profile1); // Charlie's Profile

const profile2 = user2.getProfile?.(); // user2.getProfile이 undefined이므로 호출되지 않음
console.log("User 2 profile:", profile2); // undefined

// 3. 옵셔널 배열 인덱싱
const firstRole = user3.roles?.[0]; // user3.roles가 존재하고 첫 번째 요소가 있으면 반환
console.log("User 3 first role:", firstRole); // admin

const fourthRole = user3.roles?.[3]; // user3.roles는 존재하지만 네 번째 요소는 없으므로 undefined
console.log("User 3 fourth role:", fourthRole); // undefined

const noRolesUser: UserData = { id: 4 };
const firstRoleNoUser = noRolesUser.roles?.[0]; // noRolesUser.roles가 undefined이므로 전체 표현식은 undefined
console.log("No Roles User first role:", firstRoleNoUser); // undefined

// 4. 병합 연산자 (Nullish Coalescing)와 함께 사용
// ?? 연산자는 null 또는 undefined일 때만 기본값을 제공합니다.
const userNameMaybe = user2.name ?? "Guest";
console.log("User 2 name (with default):", userNameMaybe); // David (name이 존재하므로)

const userContactEmail = user2.contact?.email ?? "[email protected]";
console.log("User 2 contact email (with default):", userContactEmail); // [email protected]

옵셔널 체이닝은 접근, 호출, 인덱싱 단계마다 값이 없으면 흐름을 멈추고, ??로 마지막 기본값을 정리할 수 있습니다.

옵셔널 체이닝의 이점
  • 코드 간결성: if (obj && obj.prop && obj.prop.subprop)와 같은 긴 중첩 null 체크 코드를 제거하여 가독성을 크게 향상시킵니다.
  • 안전성: 런타임에 TypeError: Cannot read properties of undefined (reading 'prop')와 같은 오류를 방지합니다.
  • 유지보수성: 속성 구조가 변경될 때 관련된 null 체크 로직을 일일이 수정할 필요 없이, 옵셔널 체이닝만 확인하면 됩니다.
  • 타입스크립트 통합: 타입스크립트는 옵셔널 체이닝의 결과를 정확히 추론합니다. 예를 들어, user?.contact?.email의 타입은 string | undefined로 추론됩니다.

모나드와 옵셔널 체이닝의 관계

옵셔널 체이닝은 부재일 때 다음 접근을 건너뛴다는 점에서 Maybe와 비슷하지만, 모나드의 일반적인 인터페이스나 법칙을 제공하는 기능은 아닙니다. ?.는 해당 위치의 nullish 값만 검사하며, 접근자·호출 함수가 던진 예외나 체인 밖 연산까지 보호하지 않습니다.

  • Maybe는 flatMap과 같은 메서드를 통해 컨텍스트 내부의 값을 변환하고 컨텍스트를 유지합니다. null·undefined이면 이후 콜백을 생략합니다. 내부 값이 null이더라도 반환형을 Maybe<null>로 고정하는 것이 아니라, 앞의 보완처럼 이어질 값의 타입 Maybe<U>를 유지합니다.
  • 옵셔널 체이닝(?.)은 유사하게 null 또는 undefined 값을 만나면 즉시 연산을 중단하고 undefined를 반환합니다. 이는 마치 null 컨텍스트가 전파되는 것과 같습니다.

결론적으로, 옵셔널 체이닝은 모나드의 개념을 이해하는 데 도움이 되는 매우 실용적인 예시이며, 함수형 프로그래밍의 원칙(특히 안전한 값 처리)을 따르면서도 개발자에게 편리함을 제공하는 구문 설탕입니다.

모나드는 복잡한 개념이지만, Promise, Array, 그리고 옵셔널 체이닝을 통해 이미 그 아이디어를 일상적으로 사용하고 있습니다.

함수형 프로그래밍에서 모나드는 이처럼 부수 효과(오류, 비동기, null 등)를 안전하게 캡슐화하고 순수 함수들 간의 조합을 가능하게 하는 강력한 추상화 도구로 활용됩니다.

nullish union이 value!=null 가드를 거쳐 T 사용 또는 도메인의 부재 정책으로 분기한다. ?. 보호 범위와 ??·오류를 별도 카드로 설명한다.

NULLISH / GUARD AND DOMAIN POLICY

nullish 검사와 선택적 접근·기본값의 역할을 구분한다

T | null | undefined를 정확히 표현하고 nullish 검사를 통과한 값만 좁혀 사용합니다. 부재가 허용되는 경우 기본값을 고르고, 필수 값의 부재라면 명시적 오류를 만듭니다.

nullish 검사와 선택적 접근·기본값의 역할을 구분한다nullish union이 value!=null 가드를 거쳐 T 사용 또는 도메인의 부재 정책으로 분기한다. ?. 보호 범위와 ??·오류를 별도 카드로 설명한다.T | null | undefined부재가 가능한 값value != nullnull과 undefined 검사존재부재T로 좁혀 사용다른 조건은 별도 검사도메인 부재 정책기본값 또는 명시적 오류
nullish 가드의 존재·부재 분기T 또는 null 또는 undefined 입력을 value != null로 검사한다. 존재하면 T로 좁혀 사용하고 다른 조건은 별도 검사한다. 부재면 도메인 정책에 따라 기본값 또는 명시적 오류를 고른다. 존재와 부재는 서로 이어지는 단계가 아니다. ?.와 ??의 보호 범위는 기존 아래 패널에서 구별한다.T | null | undefined부재가 가능한 값value != nullnull과 undefined 검사T로 좁혀 사용다른 조건은 별도 검사도메인 부재 정책기본값 또는명시적 오류존재부재

GUARD

falsy와 nullish는 다르다

value != null은 null·undefined를 제외합니다. 0·false·빈 문자열도 값으로 유지합니다. 이것만으로 도메인 범위나 외부 데이터 구조까지 검증되지는 않습니다.

OPTIONAL ACCESS

?.는 해당 위치만 보호

contact?.email, getProfile?.(), roles?.[0]처럼 해당 왼쪽 값이 nullish면 체인을 멈춥니다. 모든 이후 속성·체인 밖 연산·getter/함수 예외를 보호하지 않습니다.

DEFAULT

??는 부재에만 기본값

null/undefined일 때만 오른쪽을 평가합니다. ||와 달리 0·false·빈 문자열을 덮지 않습니다. 교재 user2 contact는 기본 email로 정리됩니다.

REQUIRED VALUE

필수 부재는 오류로 처리

필수 조건이라면 의미 있는 오류를 만듭니다. non-null assertion !는 runtime 검증이나 예외 처리를 추가하지 않습니다.

옵셔널 체이닝은 Maybe의 일반 인터페이스나 법칙을 구현하지 않습니다. 접근 중단과 부재의 도메인 의미를 구분합니다.

부재와 빈 값은 같은가?

앞의 두 부재 분기를 보완한 Maybe 클래스만 별도 파일에 복사한 뒤 아래 코드를 이어 실행해 보세요. 이름이 없는 user3 예제와 달리 여기서는 빈 문자열도 유효한 값으로 허용합니다.

let transformations = 0;
const emptyName = Maybe.of("")
  .map(value => { transformations++; return value.toUpperCase(); })
  .getOrElse("UNKNOWN");
const absentName = Maybe.of<string>(undefined)
  .map(value => { transformations++; return value.toUpperCase(); })
  .getOrElse("UNKNOWN");
console.log(emptyName, absentName, transformations); // 빈 문자열, UNKNOWN, 1

먼저 왜 콜백이 한 번만 실행되는지 설명하고, Maybe.of(0).getOrElse(99)의 결과를 예측하세요. 이어 이름 타입이 string | null | undefined일 때 name ?? "Guest"와 name || "Guest"가 빈 문자열을 각각 어떻게 처리하는지 적어 보세요.

nullish 검사와 truthy 검사의 차이

빈 문자열은 null·undefined가 아니므로 map의 콜백이 한 번 실행되고 빈 문자열을 반환합니다. undefined 문맥에서는 콜백을 생략해 UNKNOWN을 받습니다. 0도 값이 있으므로 getOrElse(99)의 결과는 0입니다.

??는 빈 문자열을 유지하고 ||는 falsy인 빈 문자열을 Guest로 바꿉니다. 이름이 반드시 비어 있지 않아야 하는 도메인이라면 nullish 검사 뒤 빈 문자열 검사를 별도로 추가해야 합니다. 둘 중 하나를 무조건 정답으로 선택하는 문제가 아닙니다.

검사 위치와 0·빈 문자열의 차이는 TypeScript 3.7의 옵셔널 체이닝·null 병합 설명을 함께 확인할 수 있습니다.