Ramda로 객체 다루기: 불변성과 선언적 접근

Ramda를 활용한 함수형 프로그래밍에서 객체를 다루는 핵심 원칙은 불변성입니다. 이 글에서는 객체의 속성을 읽고, 수정하고, 병합하는 다양한 방법을 살펴봅니다.

속성 읽기: prop과 친구들

객체에서 특정 속성을 추출할 때 prop을 사용합니다. 예를 들어 사용자의 출생 국가를 확인하는 함수를 만들어봅시다.

const 국가_일치 = 국가 => equals(국가);
const 출생국가_조회 = prop('birthCountry');
const 국내_출생자 = compose(국가_일치(OUR_COUNTRY), 출생국가_조회);

여기서 compose를 이용해 데이터 흐름을 명확히 표현했습니다. 먼저 prop('birthCountry')로 속성을 추출하고, 그 결과를 equals와 비교합니다.

중첩된 객체에 접근할 때는 path를 사용합니다.

const 우편번호_추출 = path(['address', 'zipCode']);
const 이름_확인 = has('name');
const 기본값_이름 = propOr('이름 미상', 'name');

path는 중간에 null이나 undefined가 있어도 에러를 던지지 않고 undefined를 반환합니다. propOrpathOr는 속성이 없을 때 기본값을 제공합니다.

여러 속성을 한 번에 선택하거나 제외할 수도 있습니다.

const 필수_정보 = pick(['name', 'age', 'email']);
const 민감_정보_제거 = omit(['ssn', 'password']);

속성 변경: assoc, dissoc, evolve

불변성을 지키면서 객체를 변경하려면 assocdissoc을 사용합니다.

const 이름_변경 = 새이름 => assoc('name', 새이름);
const 나이_삭제 = dissoc('age');
const 다중_삭제 = omit(['age', 'birthCountry']);

중첩 경로에 대한 변경도 가능합니다.

const 주소_우편번호_변경 = 새우편번호 => 
  assocPath(['address', 'zipcode'], 새우편번호);

속성을 변환 함수로 업이트할 때 evolve가 특히 유용합니다.

const 생일_축하 = evolve({ age: add(1) });
const 가격_할인 = evolve({ price: multiply(0.9), stock: dec });

evolve는 객체 형태로 변환 규칙을 정의합니다. 대상 객체에 없는 속성은 무시되므로 안전하게 사용할 수 있습니다.

체 병합: merge의 함정과 해결책

두 객체를 병합할 때 merge를 사용합니다.

const 기본_설정 = { theme: 'light', fontSize: 14, notifications: true };
const 사용자_설정_적용 = merge(기본_설정); // 주의: 오른쪽 인자가 우선

파이프라인에서 merge를 사용할 때 주의가 필요합니다. merge(a, b)b의 속성이 우선하지만, 파이프라인에서는 종종 반대 동작이 필요합니다.

const 파이프라인_병합 = flip(merge);
// 또는 Ramda 0.26+ 에서는 mergeRight 사용

const 설정_갱신 = pipe(
  파이프라인_병합({ lastModified: Date.now() }),
  evolve({ visitCount: inc })
);

여러 객체를 한 번에 병합하려면 mergeAll을 사용합니다.

const 최종_설정 = mergeAll([기본_설정, 사용자_설정, 관리자_오버라이드]);

실전 조합 예제

지금까지 배운 도구들을 조합하여 복잡한 데이터 변환을 수행봅시다.

const 사용자_정보_가공 = pipe(
  pick(['name', 'age', 'address', 'preferences']),
  evolve({
    name: toUpper,
    age: add(1),
    preferences: merge({ newsletter: false })
  }),
  assoc('updatedAt', new Date().toISOString()),
  omit(['internalNotes'])
);

이 패턴은 Redux 리듀서나 API 응답 변환에서 자주 활용됩니다. 각 단계가 순수 함수로 구성되어 있어 테스트와 추론이 용이합니다.

태그: Ramda Functional Programming immutability Object Transformation JavaScript

6월 21일 16:14에 게시됨