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 };
}

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

정리하며 ​

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

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

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