함수형 프로그래밍

  • 수학 함수를 사용하고 부수 효과를 피하는 것이 특징인 프로그래밍 패러다임
  • 부수 효과 없이 순수 함수만 사용하는 프로그래밍 스타일

함수형 코딩이라는 책을 빌려서 읽고 있다.

이 책에서는 완벽한 함수형 프로그래밍을 학문으로 보고, 실용성을 위해서는 완벽한 함수형 프로그래밍을 포기해도 된다고 이야기하고 있다.

나는 함수형 프로그래밍에 대해서 학문적으로는 잘 모른다. 하지만, 리액티브하게 구현된 소스를 구현 및 운영하면서 최대한 지키려고 하는 것들이 있다.

1. 첫 번째 특징 순수성

나는 pipe안의 stream함수들은 반드시 부수효과가 없어야한다고 생각한다.

다음의 예시로 1초 마다 현대차 주식의 가격 정보를 던져주는(emit) stream이 있다고 해보자. 나는 1초 마다 주식 가격의 정보를 받고 있지만 정보가 너무 많이 들어오는 것 같아 나만의 규칙을 정해서 필터링하기로 했다.

  • 조건 1: 무슨일이 있어도 5분 마다 console.log로 로그를 찍어 반응할 것
  • 조건 2: 이전 가격의 차이가 1000원이 넘을 때, 놀람!을 alert함수로 반응할 것

먼저 잘못된 예시를 보자.

순수함수를 사용하지 않은 잘못된 예시(부수효과가 있는)

hyundaeCarStockPriceInfoStream.pipe(
    debounceTime(300000), // 5 * 60 * 1000의 결과 => 300000ms
    pairwise(),
    map((prev, curr) => {
      if (Math.abs(curr - prev) >= 1000) {
        alert('놀람!' + ' '+ price);
        return price;
      } else {
        return price;
      }
    }),
  ).subscirbe((priceInfo) => console.log(price));

rxjs를 잘 모르시는 분에게는 어려울 수 있는 예시이다. 그러나 여기서 함수 map안의 로직만 보면된다.

  map((prev, curr) => {
      if (Math.abs(curr - prev) >= 1000) {
        alert('놀람!' + ' '+ price);
        return price;
      } else {
        return price;
      }
    }),

map함수는 pipe안의 stream을 조작하는 함수이다. 그런데, map에서 1000원의 절대값 가격 차이를 계산하는 것 동시에 자체적으로 브라우저의 webapi를 사용하여 alert를 하고 있다. 이것은 잘못된 것이다. pipe안의 로직들은 오로지 stream을 조작하는 일만 해야한다.(필터링 또는 추가하는 일만 해야한다.)

올바른 예시를 보자.

올바른 예시(순수성을 지키는)

hyundaeCarStockPriceInfoStream.pipe(
    debounceTime(300000), // 5 * 60 * 1000의 결과 => 300000ms
    pairwise(),
    map((prev, curr) => {
      if (Math.abs(curr - prev) >= 1000) {
        return { type: '놀람!', price: curr };
      } else {
        return { type: '보통', price: curr };
      }
    }),
  ).subscirbe((priceInfo) => {
    if (type === '놀람!') alert('놀람!' + ' ' + price);
    else console.log(type, price);
  });

이 예시의 map함수 로직은 오로지 stream에 type을 추가할 뿐, 다른 일은 하지 않고 있다. alert를 하는 일은 subscribe 로직에 넣었다.

이 예시가 좀 어려울 수 있다고 생각한다. 입문자에게는 stream에 영향을 미치는 것과 stream에 영향을 미치지 않는 것을 구분하는 것이 어려울 수 있다고 생각한다.

이해가 어려울 수 있으니 다시 설명하자면, console.log와 alert는 수학적 계산이 아님과 동시에 외부에 영향을 미치는 함수이다. pipe안의 함수들의 목적은 오로지 stream 조작이었고 그렇기에 console.log와 alert는 부수효과라고 하는 것이다.

2. 두 번째 특징 불변성

함수형 프로그램에서는 불변성을 중요하게 생각한다. 불변성이 어떤 것이냐면, 어떤 상수나 변수에 한번 메모리를 할당하면 절대 수정하지 않는 것이다.

입문자는 이렇게 생각할 수 있을 것이다. “그러면 let이 아닌 const를 사용하면 불변성을 지키는 것인가요?”

하지만, 잘 생각해보면 const를 쓰더라도 const 상수가 담고 있는 메모리는 변할 수 있다.(정확히 말하자면, const 상수가 가리키는 메모리) 대표적으로 배열, 객체등이 있다.

예를 들어, 배열을 선언하고 배열에 요소들을 추가한다고 해보자.

const array: number[] = [ 1, 2, 3, 4, 5 ];
array.push(6);
array.push(7);

array에는 이제 1, 2, 3, 4, 5, 6, 7이 들어있을 것이다. array라는 상수가 가르키고 있는 메모리는 6, 7이 추가되었다.

array가 변했다. const이지만 변한 것이다. 객체도 const임에도 불구하고 수정될 수가 있다.

그렇다면 불변성을 지키기 위해서는 어떻게 해야할까?

답은 간단하다. 그냥 다시 선언하면 되는 것이다.

const array0: number[] = [ 1, 2, 3, 4, 5 ];
const array1: number[] = [ ...array0, 6 ];
const array2: number[] = [ ...array1, 7 ];

다음은 자바스크립트 스프레드를 사용하여 불변성을 지킨 array1, array2의 예시이다.
array0과 array1, array2는 각각 독립된 메모리 공간에 있으며 array0을 변경하더라도 array1, array2는 변하지 않는다.

이것이 불변성을 지킨 간단한 예시이다.

3. 왜 순수성과 불변성이라는 개념이 있는 것일까?

가장 가까운 답은 부수효과를 줄이기위해서이다. 그리고 근본적인 답은 인간의 실수(프로그래머의 실수)를 줄이기위해서이다.

부수효과를 줄이면 인간의 실수가 줄어든다.

부수효과가 너무 많다보면 인간이 신경써야할 것들이 너무 많다. 기능하나를 추가 또는 수정, 삭제할 때 조건들이 너무 많은 것이다.

무슨 말인지 당장 피부로 와닿지 않을 것이다. 이 개념들의 필요성은 직접 회사에 들어가서 레거시를 뜯어 고치다보면 알게 될 것이다.

4. 결론

지금까지 함수형 프로그래밍에 자주 언급되는 순수성과 불변성의 개념에 대해 이야기해봤다. 이 개념들은 근본적으로 인간의 실수를 줄이기 위한 목적을 갖고 있다.

주의해야할 점은 함수형 프로그래밍을 억지로 끼워맞추는 것이다. 상황에 맞지 않게 억지로 끼워맞추다보면 코드가 좀 더 어려워지고 디버깅이 어렵게 된다.

컴퓨팅 파워가 강해지면서 생긴 대부분의 패러다임은 인간의 실수를 줄이기위해 탄생한 개념들이라는 것을 유념하자.

Categorized in:

Technology,