관리 메뉴

흰둥씨의 개발장

[함수형 코딩#6] 변경가능한 데이터 구조를 가진 언어에서 불변성 유지하기 본문

함수형 프로그래밍/쏙쏙 들어오는 함수형 코딩

[함수형 코딩#6] 변경가능한 데이터 구조를 가진 언어에서 불변성 유지하기

돈워리비해삐 2023. 7. 1. 03:09

동작을 읽기, 쓰기 , 읽기쓰기 로 분류하기
  1. 읽기 :
데이터를 바꾸지 않고 정보를 가져오는 것 / 데이터가 바뀌지 않기 때문에 다루기 쉬움 
  2. 쓰기 : 데이터를 바꾸는 것 /  바뀌는 값은 어디서 사용될 지 모르기 때문에 바뀌지 않도록 원칙 필요 (=> 불변성 원칙 필요)

 예시) 장바구니 동작 과정을 읽기, 쓰기로 나눠보기
ㄴ읽기/ 제품 개수 가져오기   
ㄴ읽기/ 제품이름으로 제품가져오기
ㄴ쓰기/ 제품 추가하기
ㄴ쓰기/ 제품이름으로 제품빼기
ㄴ쓰기/ 제품이름으로 제품 구매수량 바꾸기 

쓰기에서 필요한 불변성 원칙 : 카피 온 라이트(copy-on-write)
ㄴ자바스크립트는 불변성 적용하려면 직접 구현해야함


카피 온 라이트의 원칙
  1. 복사본 만들기 
  2. 복사본 변경하기(원하는 만큼)
  3. 복사본 리턴하기

//카피온 라이트 원칙 적용예시

function addElement (array, el){       //변경하려는 배열과 추가하려는 요소를 매개변수로 받아옴
	let newArray = array.slice();   //1. 복사본 만들기 
    newArray.push(el);                 //2. 복사본 변경
    return newArray;                   //3. 복사본 리턴함
 }

 

카피 온 라이트로 쓰기를 읽기로 바꾸기 

//cart.splice()를 복사하지 않고 사용함 -> cart(원본데이터)가 변경될 여지가 있음

function removeItemByName (cart, name){
	let idx = null;
    for(let i = 0; i < cart.length ; i++){
    	if(cart[i].name === name)idx = i;
      }
        if(idx !== null) cart.splice(idx, 1);  
}
//cart를 변경하지 않도록, 카피온라이트를 통해 불변성 유지 해보자 

function removeItemByName (cart, name){
	let newCart = cart.splice();               //지역 변수에 복사 하기  
	let idx = null;
    for(let i = 0; i < newCart.length ; i++){ 
    	if(newCart[i].name === name) idx = i;
      }
        if(idx !== null) newCart.splice(idx, 1); //복사본을 변경하기 
        
        return newCart;                          //복사본을 리턴하기
}

 

위의 카피온라이트를 적용한 코드에서, splice()함수부분을 따로 빼서 함수 만들어 적용하기 

//splice()부분을 빼서 removeItems()함수를 만들고 removeItemByName()에 적용하기 

function removeItems(array, idx, count){
    let copy = array.splice();   //복사하고 
    copy.splice(idx, count);     //복사본을 변경해서
    return copy;                 //복사본 리턴
}

function removeItemByName (cart, name){

    let idx = null;
    for(let i = 0; i < cart.length ; i++){ 
    	if(cart[i].name === name) idx = i;
      }
        if(idx !== null) return removeItems(cart, idx, 1);  
        //cart를 removeItems에서 copy-on-write로 만들어 줌
        
        return cart;                   
}

 

배열의 기본메소드 정리 (더 많은 메서드 정리 보러가기)

[].push(el);   //[]의 맨뒤에 el 추가 하고 새로운 length리턴 
[].pop();      //[]의 맨뒤값 삭제 하고 새로운 length리턴 

[].unshift(el);//[]의 맨앞에 el 를 추가한 배열리턴
[].shift();    //[]의 맨앞 값 지우고 지운 배열을 리턴

[].slice();    //배열 얇게 복사 
[].splice(idx, num);   //배열 idx위치에서 num개 항목지움

[1,2,3,4,5,6].splice[2,3];  // [1,2,6]

 

읽기랑 쓰기 동작을 함께 하는 경우
한개의 함수에서 읽기, 쓰기 값을 두개 리턴하는 것보다 아래와 같이 처리하는 것이 좋음
  1. 읽기와 쓰기 함수로 각각 나누고
  2. 읽기함수에서 리턴, 쓰기 함수에서 카피 온 라이트 적용하기

 

액션 : 변경가능한 데이터를 읽는 것 
계산 : 불변 데이터 구조를 읽는 것 
어떤 데이터에 "쓰기"가 없으면 변경 불가능한 데이터임
쓰기 => 읽기로 바꾸면 코드에 계산이 많아지고 액션은 줄어듦

불변 데이터 ? 쓰기가 없는 데이터 (데이터 생성 이후 바뀌지 않음)

  - 불변 데이터의 좋은 점 

  1. 언제든 최적화 할수 있음 (불변 데이터 사용시 느린 부분이 있으면 그 때 최적화할것)
  2. 가비지 콜렉터가 생각보다 빠름 
  3. 얕은 복사(=구조적 공유, 데이터 구조의 최상위 단계만 복사)는 메모리 참조(주소값)만 복사하기 때문에 생각보다 많이 복사하지 않음 
  4. 함수형 프로그래밍 언어에는 빠른 구현체가 있음 (적은 메모리 사용하고, 낭비 적어서 가비지 콜렉터 부담 줄여줌)

객체에 카피온라이트 적용하기 (=> 배열과 같은 단계로 구현, 복사본만들고, 복사본을 변경하고, 복사본리턴)

let obj = {
	a : 1,
    b : 2,
    }
    
let objCopy = Object.assign({}, obj); //객체를 복사하는 방법
console.log(objCopy));                // {a: 1, b: 2}

console.log(delete objCopy["a"]);  //복사본에서 키 a지우기
console.log(objCopy);              // {b: 2}

objCopy.a = 1;         // 객체 키와 값 추가 
console.log(objCopy);  // {b: 2, a: 1}

console.log(Object.keys(objCopy));  //키 목록 가져오기 ['b', 'a']

objCopy["c"] = 3;       // 객체 키와 값 추가
console.log(objCopy);   // {b: 2, a: 1, c: 3}


중첩된 배열을 얕은 복사해서 카피온라이트 하는 과정에서...
 얕은 복사는 중첩데이터에서 최상위 데이터구조(1depth)만 복사 하는 점 유의하기