도서

쏙쏙 들어오는 함수형 코딩 [CHAPTER 6]

zero2-pooh 2023. 7. 30. 02:41

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

🖐🏻이번 장에서 살펴볼 내용

  • 데이터가 바뀌지 않도록 하기 위해 카피-온-라이트를 적용한다.
  • 배열과 객체를 데이터에 쓸 수 있는 카피-온-라이트 동작을 만든다.
  • 깊이 중첩된 데이터도 카피-온-라이트가 잘 동작하게 만든다.

중첩된 데이터란?

- 데이터 구조 안에 데이터 구조가 있는 경우 데이터가 중첩되었다고 말한다.

- 배열안에 객체가 있다면 중첩된 데이터이다. 👉🏻 이런 경우에 객체가 배열 안에 중첩되었다고 볼 수 있다. 

 

어떻게 하면 중첩된 데이터에 대한 불변 동작을 구현할 수 있을까?


동작을 읽기, 쓰기 또는 둘 다로 분류하기

동작을 읽기 또는 쓰기 또는 둘 다 하는 것으로 분류할 수 있다.

 

읽기
  • 데이터를 바꾸지 않고 정보를 꺼내는 것이다.  
  • 데이터가 바뀌지 않기 때문에 다루기 쉽다. 
  • 만약 인자에만 의존해 정보를 가져오는 읽기 동작이라면 계산이라고 할 수 있다.

 

쓰기
  • 어떻게든 데이터를 바꾼다.
  • 바뀌는 값은 어디서 사용될지 모르기 때문에 바뀌지 안호록 원칙이 필요하다.

장바구니 동작으로 예를 들어 보겠다.

  1.  제품 개수 가져오기. (읽기)
  2.  제품 이름으로 제품 가져오기. (읽기)
  3.  제품 추가하기. (쓰기)
  4.  제품 이름으로 제품 빼기. (쓰기)
  5.  제품 이름으로 제품 구매 수량 바꾸기. (쓰기)

카피-온-라이트 원칙 세 단계

카피-온-라이트는 세 단계로 되어 있다. 

각 단계를 구현하면 카피-온-라이트로 동작한다.

장바구니 전역변수를 변경하는 동작을 모두 카피-온-라이트로 바꾸면, 장바구니는 더 이상 변경되지 않는다. 

❗️따라서 불변 데이터로 동작한다. 

 

 

아래 세 단계로 카피-온-라이트를 적용하면 불변성을 유지하면서 값을 바꿀 수 있다.

 

  1. 복사본 만들기
  2. 복사본 변경하기(원하는 만큼)
  3. 복사본 리턴하기
💡카피-온-라이트는 쓰기를 ➡️ 읽기로 바꾼다!! (변경 불가능한 불변성으로 만드려면 카피-온-라이트를 쓰자.)

쓰기를 하면서 읽기도 하는 동작은 어떻게 해야 할까?

어떤 동작은 읽고 변경하는 일을 동시에 한다. 

이런 동작은 값을 변경하고 리턴한다.  .shift() 메서드가 좋은 예제인데 한 번 살펴보자.

 

let a = [1,2,3,4];
let b = a.shift();

console.log(b);  // 1을 출력
console.log(a);  // [2,3,4]를 출력

.shift() 메서드는 값을 바꾸는 동시에 배열에 첫 번째 항목을 리턴한다. 변경하면서 읽는 동작이다. 

 


☝🏻위 동작을 카피-온-라이트로 어떻게 바꿀 수 있을까?

 

카피-온-라이트에서 쓰기를 읽기로 바꿨다. 

👉🏻 읽기라는 말은 값을 리턴한다는 의미이다. 

❗️하지만 .shift() 메서드는 이미 읽기 이다! (값을 리턴하고 있기 때문.)

 

그래서 접근 방법은?? 두 가지가 있다.

 

  1. 읽기와 쓰기 함수로 각각 분리한다.
  2. 함수에서 값을 두 개 리턴한다.

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

쓰면서 읽기도 하는 함수를 분리하는 작업은 두 단계로 나눌 수 있다. 

 

  1. 읽기와 쓰기 동작으로 분리하기
  2. 쓰기 동작을 카피-온-라이트로 바꾸기
💡 읽기와 쓰기를 분리하는 접근 방법은 분리된 함수를 따로 쓸 수 있기 때문에 더 좋은 접근 방법이다.
물론 함께 쓸 수도 있다. 원래는 무조건 함께 쓸 수밖에 없었지만 이제 선택해서 쓸 수 있다. 

값을 두 개 리턴하는 함수로 만들기

첫 번째 접근 방법처럼 두 번째 접근 방법도 ➡️ 두 단계로 나눌 수 있다. 

먼저 .shift() 메서드를 바꿀 수 있도록 새로운 함수로 감싼다. 

 

 

1. 동작을 감싸기

function shift(array){
	return array.shift();
}

.shift() 메서드를 바꿀 수 있도록 새로운 함수로 감싸는 것이다. 

❗️여기서 함수 리턴값을 무시하면 안 된다!!

 

 

2. 읽으면서 쓰기도 하는 함수를 읽기 함수로 바꾸기

-인자를 복사한 후에 복사한 값의 첫 번째 항목을 지우고 , 지운 첫 번째 항목과 변경된 배열을 함께 히턴하도록 바꾼다. 

 

3. 다른 방법

- 또 다른 방법으로는 첫 번째 접근 방식을 사용해 두 값을 객체로 조합하는 방법이다.


불변 데이터 구조를 읽는 것은 계산이다

 

변경 가능한 데이터를 읽는 것은 액션이다

- 변경 가능한 값을 읽을 때마다 다른 값을 읽을 수도 있다. 따라서 변경 가능한 데이터를 읽는 것은 액션이다. 

 

 

쓰기는 데이터를 변경 가능한 구조로 만든다

- 쓰기는 데이터를 바꾸기 때문에 데이터를 변경 가능한 구조로 만든다.

 

어떤 데이터에 쓰기가 없다면 데이터는 변경 불가능한 데이터다

- 쓰기를 모두 없앴다면 데이터는 생성 이후 바뀌지 않는다. 따라서 불변 데이터이다. 

 

불변 데이터 구조를 읽는 것은 계산이다

- 어떤 데이터를 불변형으로 만들었다면 그 데이터에 모든 읽기는 계산이다.

 

쓰기를 읽기로 바꾸면 코드에 계산이 많아진다

- 데이터 구조를 불변형으로 만들수록 코드에 더 많은 계산이 생기고 액션은 줄어든다. 

 


불변 데이터 구조는 충분히 빠르다

- 일반적으로 불변 데이터 구조는 변경 가능한 데이터 구조보다 메모리를 더 많이 쓰고 느리다. 

❗️하지만 불변 데이터 구조를 사용하면서 대용량의 고성능 시스템을 구현하는 사례는 많이 있다.

 

👉🏻 이런 사례는 불변 데이터도 일반 애플리케이션에 쓰기 충분히 빠르다는 증거이다. 

그래도 몇 가지 논점이 있는데,

 

언제든 최적화할 수 있다

- 애플리케이션을 개발할 때 예상하기 힘든 병목 지점이 항상 있다. 

그래서 성능 개선을 할 때는 보통 미리 최적화하지 말라고 한다.

🙌🏻 불변 데이터 구조를 사용하고 속도가 느린 부분이 있다면 그때 최적화 하자!

 

 

가비지 콜렉터는 매우 빠르다

 

생각보다 많이 복사하지 않는다

- 데이터 구조의 최상위 단계만 복사하는 것을 얕은 복사라고 한다.

- 얕은 복사는 같은 메모리를 가리키는 참조에 대한 복사본을 만든다. 👉🏻 이것을 구조적 공유라고 한다 

 

함수형 프로그래밍 언어에는 빠른 구현체가 있다

- 어떤 함수형 프로그래밍 언어는 언어에서 불변 데이터 구조를 지원한다. 

- 그리고 직접 만든 것보다 더 효율적으로 동작한다

 


 

🖐🏻 요점 정리

  • 함수형 프로그래밍에서 불변 데이터가 필요하다. 계산에서는 변경 가능한 데이터에 쓰기를 할 수 없다. 
  • 카피-온-라이트는 데이터를 불변형으로 유지할 수 있는 원칙이다. 복사본을 만들고 원본 대신 복사본을 변경하는 것을 말한다. 
  • 카피-온-라이트는 값을 변경하기 전에 얕은 복사를 한다. 그리고 리턴한다. 이렇게 하면 통제할 수 있는 범위에서 불변성을 구현할 수 있다.
  • 보일러 플레이트 코드를 줄이기 위해 기본적인 배열과 객체 동작에 대한 카피-온-라이트 버전을 만들어 두는 것이 좋다.