쏙쏙 들어오는 함수형 코딩 [CHAPTER 6]
변경 가능한 데이터 구조를 가진 언어에서 불변성 유지하기
🖐🏻이번 장에서 살펴볼 내용
- 데이터가 바뀌지 않도록 하기 위해 카피-온-라이트를 적용한다.
- 배열과 객체를 데이터에 쓸 수 있는 카피-온-라이트 동작을 만든다.
- 깊이 중첩된 데이터도 카피-온-라이트가 잘 동작하게 만든다.
중첩된 데이터란?
- 데이터 구조 안에 데이터 구조가 있는 경우 데이터가 중첩되었다고 말한다.
- 배열안에 객체가 있다면 중첩된 데이터이다. 👉🏻 이런 경우에 객체가 배열 안에 중첩되었다고 볼 수 있다.
어떻게 하면 중첩된 데이터에 대한 불변 동작을 구현할 수 있을까?
동작을 읽기, 쓰기 또는 둘 다로 분류하기
동작을 읽기 또는 쓰기 또는 둘 다 하는 것으로 분류할 수 있다.
읽기
- 데이터를 바꾸지 않고 정보를 꺼내는 것이다.
- 데이터가 바뀌지 않기 때문에 다루기 쉽다.
- 만약 인자에만 의존해 정보를 가져오는 읽기 동작이라면 계산이라고 할 수 있다.
쓰기
- 어떻게든 데이터를 바꾼다.
- 바뀌는 값은 어디서 사용될지 모르기 때문에 바뀌지 안호록 원칙이 필요하다.
장바구니 동작으로 예를 들어 보겠다.
- 제품 개수 가져오기. (읽기)
- 제품 이름으로 제품 가져오기. (읽기)
- 제품 추가하기. (쓰기)
- 제품 이름으로 제품 빼기. (쓰기)
- 제품 이름으로 제품 구매 수량 바꾸기. (쓰기)
카피-온-라이트 원칙 세 단계
카피-온-라이트는 세 단계로 되어 있다.
각 단계를 구현하면 카피-온-라이트로 동작한다.
장바구니 전역변수를 변경하는 동작을 모두 카피-온-라이트로 바꾸면, 장바구니는 더 이상 변경되지 않는다.
❗️따라서 불변 데이터로 동작한다.
아래 세 단계로 카피-온-라이트를 적용하면 불변성을 유지하면서 값을 바꿀 수 있다.
- 복사본 만들기
- 복사본 변경하기(원하는 만큼)
- 복사본 리턴하기
💡카피-온-라이트는 쓰기를 ➡️ 읽기로 바꾼다!! (변경 불가능한 불변성으로 만드려면 카피-온-라이트를 쓰자.)
쓰기를 하면서 읽기도 하는 동작은 어떻게 해야 할까?
어떤 동작은 읽고 변경하는 일을 동시에 한다.
이런 동작은 값을 변경하고 리턴한다. .shift() 메서드가 좋은 예제인데 한 번 살펴보자.
let a = [1,2,3,4];
let b = a.shift();
console.log(b); // 1을 출력
console.log(a); // [2,3,4]를 출력
.shift() 메서드는 값을 바꾸는 동시에 배열에 첫 번째 항목을 리턴한다. 변경하면서 읽는 동작이다.
☝🏻위 동작을 카피-온-라이트로 어떻게 바꿀 수 있을까?
카피-온-라이트에서 쓰기를 읽기로 바꿨다.
👉🏻 읽기라는 말은 값을 리턴한다는 의미이다.
❗️하지만 .shift() 메서드는 이미 읽기 이다! (값을 리턴하고 있기 때문.)
그래서 접근 방법은?? 두 가지가 있다.
- 읽기와 쓰기 함수로 각각 분리한다.
- 함수에서 값을 두 개 리턴한다.
쓰면서 읽기도 하는 함수를 분리하기
쓰면서 읽기도 하는 함수를 분리하는 작업은 두 단계로 나눌 수 있다.
- 읽기와 쓰기 동작으로 분리하기
- 쓰기 동작을 카피-온-라이트로 바꾸기
💡 읽기와 쓰기를 분리하는 접근 방법은 분리된 함수를 따로 쓸 수 있기 때문에 더 좋은 접근 방법이다.
물론 함께 쓸 수도 있다. 원래는 무조건 함께 쓸 수밖에 없었지만 이제 선택해서 쓸 수 있다.
값을 두 개 리턴하는 함수로 만들기
첫 번째 접근 방법처럼 두 번째 접근 방법도 ➡️ 두 단계로 나눌 수 있다.
먼저 .shift() 메서드를 바꿀 수 있도록 새로운 함수로 감싼다.
1. 동작을 감싸기
function shift(array){
return array.shift();
}
.shift() 메서드를 바꿀 수 있도록 새로운 함수로 감싸는 것이다.
❗️여기서 함수 리턴값을 무시하면 안 된다!!
2. 읽으면서 쓰기도 하는 함수를 읽기 함수로 바꾸기
-인자를 복사한 후에 복사한 값의 첫 번째 항목을 지우고 , 지운 첫 번째 항목과 변경된 배열을 함께 히턴하도록 바꾼다.
3. 다른 방법
- 또 다른 방법으로는 첫 번째 접근 방식을 사용해 두 값을 객체로 조합하는 방법이다.
불변 데이터 구조를 읽는 것은 계산이다
변경 가능한 데이터를 읽는 것은 액션이다
- 변경 가능한 값을 읽을 때마다 다른 값을 읽을 수도 있다. 따라서 변경 가능한 데이터를 읽는 것은 액션이다.
쓰기는 데이터를 변경 가능한 구조로 만든다
- 쓰기는 데이터를 바꾸기 때문에 데이터를 변경 가능한 구조로 만든다.
어떤 데이터에 쓰기가 없다면 데이터는 변경 불가능한 데이터다
- 쓰기를 모두 없앴다면 데이터는 생성 이후 바뀌지 않는다. 따라서 불변 데이터이다.
불변 데이터 구조를 읽는 것은 계산이다
- 어떤 데이터를 불변형으로 만들었다면 그 데이터에 모든 읽기는 계산이다.
쓰기를 읽기로 바꾸면 코드에 계산이 많아진다
- 데이터 구조를 불변형으로 만들수록 코드에 더 많은 계산이 생기고 액션은 줄어든다.
불변 데이터 구조는 충분히 빠르다
- 일반적으로 불변 데이터 구조는 변경 가능한 데이터 구조보다 메모리를 더 많이 쓰고 느리다.
❗️하지만 불변 데이터 구조를 사용하면서 대용량의 고성능 시스템을 구현하는 사례는 많이 있다.
👉🏻 이런 사례는 불변 데이터도 일반 애플리케이션에 쓰기 충분히 빠르다는 증거이다.
그래도 몇 가지 논점이 있는데,
언제든 최적화할 수 있다
- 애플리케이션을 개발할 때 예상하기 힘든 병목 지점이 항상 있다.
그래서 성능 개선을 할 때는 보통 미리 최적화하지 말라고 한다.
🙌🏻 불변 데이터 구조를 사용하고 속도가 느린 부분이 있다면 그때 최적화 하자!
가비지 콜렉터는 매우 빠르다
생각보다 많이 복사하지 않는다
- 데이터 구조의 최상위 단계만 복사하는 것을 얕은 복사라고 한다.
- 얕은 복사는 같은 메모리를 가리키는 참조에 대한 복사본을 만든다. 👉🏻 이것을 구조적 공유라고 한다
함수형 프로그래밍 언어에는 빠른 구현체가 있다
- 어떤 함수형 프로그래밍 언어는 언어에서 불변 데이터 구조를 지원한다.
- 그리고 직접 만든 것보다 더 효율적으로 동작한다
🖐🏻 요점 정리
- 함수형 프로그래밍에서 불변 데이터가 필요하다. 계산에서는 변경 가능한 데이터에 쓰기를 할 수 없다.
- 카피-온-라이트는 데이터를 불변형으로 유지할 수 있는 원칙이다. 복사본을 만들고 원본 대신 복사본을 변경하는 것을 말한다.
- 카피-온-라이트는 값을 변경하기 전에 얕은 복사를 한다. 그리고 리턴한다. 이렇게 하면 통제할 수 있는 범위에서 불변성을 구현할 수 있다.
- 보일러 플레이트 코드를 줄이기 위해 기본적인 배열과 객체 동작에 대한 카피-온-라이트 버전을 만들어 두는 것이 좋다.