• Blog

  • Snippets

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

May 13, 2024

쏙쏙들어오는 함수형 코딩

# 동작을 읽기와 쓰기로 분류하기

읽기(read)

쓰기(write)

이 중에서 쓰기는 불변성 원칙에 따라 구현해야함.
불변성 원칙은 카피-온-라이트(copy-on-write)라고 함.

# 카피-온-라이트(copy-on-write)

카피-온-라이트에는 원칙이 있다.

  1. 복사본 만들기
  2. 복사본 변경하기(원하는 만큼)
  3. 복사본 리턴하기

이 원칙을 지키면서 카피-온-라이트를 사용한다면 불변성을 지킬 수 있음.

카피-온-라이트 사용예시

function add_element_last(array, item) {
var new_array = array.slice();
new_array.push(item);
return new_array;
}

add_element_last는 쓰기(write)일까 읽기(read)일까? 값을 복사했고, 원본 데이터를 변경하지 않고 복사한값을 리턴했기때문에 읽기(read)라고 할 수 있음.

카피-온-라이트는 읽기를 쓰기로 바꿈.

불변성을 고려하지 않은 코드를 수정해보자

이전 코드

function remove_item_by_name(cart, name) {
var idx = null;
for(var i=0; i<cart.length; i++) {
if(cart[i].name === name) {
idx = i;
}
}
if(idx !== null) {
cart.splice(idx, 1)
}
}

수정 한 코드

function remove_item_by_name(cart, name) {
var new_cart = cart.slice(); // 복사본 만들고
var idx = null;
for(var i=0; i<new_cart.length; i++) {
if(new_cart[i].name === name) {
idx = i;
};
};
if(idx !== null) {
new_cart.splice(idx, 1) // 복사본 수정 후
}
return new_cart; // 복사본 리턴
}

카피-온-라이트 연습 문제

밑에 있는 코드를 카피-온-라이트 형식으로 바꿔보세요.

var mailing_list = [];
function add_contact(email) {
mailing_list.push(email);
}
function submit_form_handler(event) {
var form = event.target;
var email = form.elements["email"].value;
add_contact(email);
}

나의 풀이

var mailing_list = [];
function add_contact(list, email) {
var new_mailing_list = list.slice();
new_mailing_list.push(email);
return new_mailing_list;
}
function submit_form_handler(event) {
var form = event.EventTarget;
var email = form.elements["email"].value;
mailing_list = add_contact(mailing_list, email);
}

의도와 맞게 잘 풀었음. 전역변수를 받아서 복사본을 만들고 수정후 복사본을 리턴한 결과값을 사용부에서 전역변수에 할당했음.

# 쓰면서 읽기도 하는 함수를 분리하기

위와 같이 두 가지 방법이 있음.

읽기 함수와 쓰기 함수로 분리하기 예시

// 읽기 함수
function first_element(array) {
return array[0];
}
// 쓰기 함수
function shift() {
array.shift();
}

값 두개를 리턴하는 함수로 만들기 예시

function shift(array) {
var array_copy = array.slice();
var first = array_copy.shift();
return {
first,
array: array_copy
}
}

연습 문제1.

push 메서드를 카피-온-라이트로 만들어보세요.

나의 풀이

function copy_on_write_push(array, item) {
const copy_array = array.slice();
copy_array.push(item);
return copy_array;
}

정답임.

연습 문제2.

카피-온-라이트 버전의 push함수를 사용해 add_contact 함수를 리팩토링 해보세요.

이전 코드

function add_contact(mailing_list, email) {
var list_copy = mailing_list.slice();
list_copy.push(email);
return list_copy;
}

나의 코드

function add_contact(mailing_list, email) {
return copy_on_write_push(mailing_list, email)
}

정답임.

연습 문제3.

배열 항목을 카피-온-라이트 방식으로 설정하는 arraySet 함수를 만들아보세요.

예시

a[15] = 2;

나의 풀이

function arraySet(array, idx, value) {
var list_copy = array.slice();
list_copy[idx] = value;
return list_copy;
}

정답임.

# 객체에 대한 카피-온-라이트

배열에서 카피-온-라이트를 하는 방법은 slice가 있다면, 객체에는 Object.assign이 있음.

객체 카피-온-라이트 예시

var object = {a: '1', b: '2'};
var copy_object = Object.assign({}, object);

Object.assign으로 객체를 복사 할 수 있지만, 중첩(nested)된 객체 구조는 다 복사하지 못함. 이유는 Object.assign은 얕은복사이기 때문임.

#참고 얕은 복사는 중첩데이터에서 최상위 데이터만 복사함.