Skip to content

chp6. 변경 가능한 데이터 구조를 가진 언어에서 불변성 유지하기

함수형 프로그래밍에서는 액션을 줄이고 계산을 늘리는 것이 핵심입니다. 이를 가능하게 하는 중요한 개념이 바로 불변성(immutability) 입니다.

이번 장에서는 특히 배열에서 불변성을 유지하는 방법을 중심으로 살펴보겠습니다.

1. 동작을 읽기와 쓰기로 구분하기

함수의 동작은 크게 두 가지로 나눌 수 있습니다.

  • 읽기(Read): 데이터를 바꾸지 않고 정보를 꺼내는 것
  • 쓰기(Write): 데이터를 변경하는 것

예를 들어, 아래 함수는 배열에 요소를 추가하면서 원본 배열을 직접 수정하므로 쓰기 동작입니다.

tsx
function add_element_last(array, elem) {
    array.push(elem);
}

불변성을 유지하려면 원본을 직접 수정하지 말고, 쓰기 동작을 읽기 방식으로 바꾸는 전략이 필요합니다. 이때 사용되는 원칙이 바로 카피-온-라이트(Copy-on-Write) 입니다.

2. 카피-온-라이트 원칙

카피-온-라이트는 다음 세 단계로 이루어집니다.

  1. 복사본 만들기
  2. 복사본 변경하기
  3. 복사본 반환하기

위의 add_element_last 함수를 카피-온-라이트로 구현하면 다음과 같습니다.

tsx
function add_element_last(array, elem) {
    var new_array = array.slice(); // 1. 복사본 만들기
    new_array.push(elem); // 2. 복사본 변경하기
    return new_array; // 3. 복사본 반환하기
}

이제 원본 배열은 그대로 두고, 새로운 배열만 생성됩니다. 즉, 불변성이 지켜지면서도 원하는 변경을 적용할 수 있습니다.

3. 읽기와 쓰기를 모두 하는 동작 다루기

어떤 동작은 읽기와 쓰기를 동시에 수행합니다.

예를 들어 array.shift()는 첫 요소를 반환(읽기)하면서 동시에 배열에서 제거(쓰기)합니다.

이런 경우 불변성을 유지하려면 두 가지 접근이 있습니다.

3.1 읽기와 쓰기로 분리하기

읽기와 쓰기를 별도의 함수로 나누어 책임을 분리합니다.

tsx
// 읽기
function first_element(array) {
    return array[0];
}

// 쓰기
function drop_first(array) {
    var new_array = array.slice();
    new_array.shift();
    return new_array;
}

이 방식은 각 함수가 한 가지 역할만 담당하므로 테스트와 재사용이 더 용이합니다.

3.2 두 개 값을 함께 반환하기

읽기와 쓰기를 동시에 처리해야 한다면, 두 값을 묶어서 반환할 수도 있습니다.

tsx
function shift(array) {
    var array_copy = array.slice();
    var first = array_copy.shift();

    return { first, array: array_copy };
}

이 방식은 한 번의 호출로 필요한 정보를 모두 얻을 수 있다는 장점이 있습니다. 다만, 함수의 책임이 늘어나는 만큼 상황에 따라 적절히 선택하는 것이 좋습니다.

정리하며

불변성을 유지하는 것은 원본을 보호하면서 새로운 값을 만들어내는 것입니다.

  • 읽기와 쓰기를 명확히 구분하고,
  • 카피-온-라이트 원칙을 적용하며,
  • 필요하다면 책임을 분리하거나 결과를 묶어 반환하는 방식으로 다룬다면,

변경 가능한 데이터 구조를 가진 언어에서도 불변성을 지킬 수 있습니다.