순수함수, 부수효과(사이드이펙트), 상태변이

SPA 프레임워크를 사용하다보면 함수형, 안전한 동시성 측면에서 순수함수는 필수적이다.

순수함수가 무엇인지 다루기 전에 함수를 자세히 살펴보는 것이 좋다. 함수형 프로그래밍을 이해하기 쉽게 만들 수 있다.

함수란?

함수는 인수라고하는 입력을 받아 반환값이라는 출력을 생성하는 프로세스이다.

  • Mapping : 주어진 입력을 기반으로 일부 출력을 생성한다. 함수는 입력값을 출력값에 매핑한다.
  • Procedures : 일련의 단계를 수행하기 위해 함수가 호출될 수 있다. 시퀀스는 프로시저라고 하며, 스타일의 프로그래밍을 절차 프로그래밍이라고 한다.
  • I/O : 대개 함수는 화면, Storage, 시스템 로그 또는 네트워크와 같은 시스템의 다른 부분과 통신하기 위한 기능이 있다.

Mapping

순수함수는 모두 매핑에 관한 것이다. 함수는 입력 인수를 반환값과 매핑한다. 즉, 각각 입력 집합에 대해 출력이 존재한다. 함수는 입력을 받아 해당 출력을 반환한다.

Math.max는 인수들 중에서 가장 큰 수를 반환한다.

Math.max(2, 8, 5); // 8

예를 들어 위와 같이 2, 8, 5이 들어간다. Math.max는 많은 수의 인수를 취하여 가장 큰 인수값을 반환하는 함수이다. 이 경우에 전달된 가장 큰 숫자는 8이면서 반환값이다.

함수는 컴퓨팅과 수학에서 정말로 중요하다. 유용한 방법으로 데이터를 처리하는데 도움이 된다. 훌륭한 프로그래머는 함수를 설명하는 이름을 지정하여 코드를 볼 때 함수 이름을 보고 함수의 기능을 이해할 수 있다.

수학에도 함수가 있으며 Javascript의 함수의 매우 유사하게 작동한다.

f(x) = 2x

f라는 함수를 선언하고 x라는 인수를 사용하고 x에 2를 곱한다. 이 함수를 사용하려면 간단히 x값을 제공하면 된다.

f(2)

대수학에서 이것은 쓰기와 정확히 같은 의미이다.

4

따라서 f(2)라 표시된 곳은 4로 대체할 수 있다.

이제 해당 함수를 JavaScript로 변환해보자.

const double = x => x * 2;

console.log()을 사용하여 함수의 출력을 검사할 수 있다.

console.log( double(5) ); // 10

수학함수에서 f(2)를 4로 바꿀 수 있다고 말했다. 이 경우 JavaScript엔진은 double(5)를 10으로 대체한다. double()은 순수함수이다.

순수함수

순수함수란 아래의 의미를 가진다.

  • 동일한 입력이 주어지면 항상 동일한 출력을 반환한다.
  • 사이드이펙트가 없다.

순수함수를 사용하여 프로그램 요구사항을 구현하는 것이 실용적이라면 다른 선택사항보다 더더욱 사용해야한다. 순수함수는 입력을 받아 해당 입력에 따라 출력을 반환한다. 프로그램에서 재사용 가능한 가장 간단한 코드 블록으로 컴퓨터 과학에서 가장 중요한 디자인 원칙 KISS(Keep It Simple, Stupid) 일 것이다.

순수함수는 외부상태와 완전히 독립적이므로 공유가능한 변경 가능한 상태와 관련된 버그의 전체 클래스에 영향을 받지 않는다. 또한 독립적인 특성으로 인해 많은 CPU 및 전체 분산 컴퓨팅 클러스터에서 병렬처리를 수행할 수 있는 훌륭한 후보가 되므로 많은 과학 및 리소스 집약적 컴퓨팅 작업에 필수적이다.

순수함수는 코드에서 쉽게 이동하고, 리팩토링하고, 재구성하기가 매우 독립적이므로 프로그램을 보다 유연하고 향후 변경에 적용할 수 있다.

공유상태의 문제점

모든 종류의 비동기 작업 또는 동시성으로 인해 유사한 경쟁 조건이 발생할 수 있다. 출력이 제어할 수 없는 이벤트 시퀀스에 의존하는 경우 경쟁조건이 발생한다. 실제로 공유 상태를 사용하고 있고, 그 상태가 모든 의도와 목적으로 결정적 요인에 따라 달라지는 시퀀스에 의존하는 경우 출력을 예측할 수 없으므로 제대로 테스트하거나 완전히 이해하는 것이 불가능하다.

JS가 단일 스레드에서 실행되므로 병렬 처리 문제에 영향을 받지 않으므로 괜찮다고 생각할 수 도 있지만, AJAX 예제에서 알 수 있듯이 단일 스레드 JS엔진은 동시성이 없음을 의미하지는 않는다. 반대로 JavaScript에는 많은 동시성 소스가 있다. API I/O, 이벤트 리스너, 웹 워커, iframe 및 시간초과는 모두 프로그램에 불확실성을 유발할 수 있다. 이것이 공유 상태와 결합하게 되면 버그를 만들어 낼 수 있다.

순수함수를 사용하면 이러한 버그를 피할 수 있다.

동일한 입력이 주어지면, 항상 같은 출력을 반환

double() 함수를 사용하면 함수 호출을 결과로 대체할 수 있으며 프로그램은 동일한 것을 의미한다. double(5)는 상관없이 프로그램에서 항상 10과 동일한 것을 의미한다. 아무리 해도 동일할 것이다.

그러나 모든 함수에 대해 똑같은 것을 말할 수는 없다. 일부 함수는 결과를 생성하기 위해 전달하는 인수 이외의 정보에 의존한다.

Math.random(); // => 0.4011148700956255
Math.random(); // => 0.8533405303023756
Math.random(); // => 0.3550692005082965

함수 호출에 인수를 전달하지는 않았지만 모두 다른 출력을 생성했다. 이는 Math.random ()는 순수하지 않다는 것을 의미한다. Math.random()은 실행할 때마다 0에서 1사이의 새로운 난수를 생성하므로 프로그램의 의미를 변경하지 않고 0.4011148700956255로 대체 할 수 없다.

함수는 동일한 입력이 주어지면 항상 동일한 출력을 생성하는 경우에만 순수하다. 대수학 클래스에서 이 규칙을 기억할 있다. 동일한 입력값이 항상 동일한 출력 값에 매핑된다. 그러나 많은 입력값이 동일한 출력값에 매핑될 수 있다.

예를 들어 다음 기능은 순수하다.

const highpass = (cutoff, value) => value >= cutoff;

동일한 입력 값은 항상 동일한 출력 값에 매핑됩니다.

highpass(5, 5); // => true
highpass(5, 5); // => true
highpass(5, 5); // => true

많은 입력 값이 동일한 출력 값에 매핑 될 수 있습니다.

highpass(5, 123); // true
highpass(5, 6);   // true
highpass(5, 18);  // true
highpass(5, 1);   // false
highpass(5, 3);   // false
highpass(5, 4);   // false

순수함수는 더이상 결정적이거나 참조저긍로 투명하지 않기 때문에 외부 변경가능 상태에 의존해서는 안된다.

순수함수는 사이드이펙트가 없다.

순수함수는 사이트이펙트를 일으키지 않으므로 외부 상태를 변경할 수 없다.

불변성

JavaScript의 객체 인수는 참조이다. 즉, 함수가 객체 또는 배열 매개변수의 속성르 변경하는 경우 함수 외부에서 액세스 할 수 있는 상태가 변경된다. 순수함수는 외부 상태를 변경해서는 안된다.

순수함수는 결정론적이다.

항상 같은 입력하면 해당 함수는 같은 결과를 출력한다.

const add = (x, y) => x + y // A pure function

add는 출력이 전적으로 인수에 의존하기 때문에 순수함수이다. 결국 동일한 값을 지정하게 되면 동일한 출력을 생성한다.

그렇다면 아래와 같다면?

const magicLetter = '*'
const createMagicPhrase = (phrase) => `${magicLetter}abra${phrase}`

위와 같은 함수가 있다면 해당 점위 외부의 값에 따라 달라지게 된다. 그러므로 이는 순수함수라 할 수 없다.

불순수함수

const fetchLoginToken = externalAPI.getUserToken

위와 같은 함수는 어떠한가? 순수함수라 할 수 있을까? 전혀 그렇지 않을 것이다. 어떤 때는 잘 내려고 어떤때는 500 error를 내릴 수도 있다. 하물며 이 호출이 deprecated가 될 수도 있는 것이다.

따라서 이 함수는 결정적이지 않기 때문에 불순수함수라 할 수 있다.

순수함수는 사이드이펙트를 일으키지 않는다.

사이드이펙드란 외부 세계에서 관찰할 수 있는 시스템의 모든 변경을 말한다.

const calculateBill = (sumOfCart, tax) => sumOfCart * tax

calculateBill는 순수한가? 이에 필요한 특징이 2가지가 있다고 설명했었다.

  1. 함수는 결과를 생성하기 위해 인수에만 의존한다.
  2. 함수는 사이드이펙트를 발생시키지 않는다.

사이트이펙트에는 아래의 내용이 가이드에 포함되지만 이외에도 있을 수 있다.

  • 파일시스템 변경
  • 데이터베이스에 기록
  • http call
  • 데이터 변경
  • 화면 그리기 / 로깅 / console
  • 사용자 입력
  • DOM 쿼리
  • 시스템 상태에 접근
  • Math.random()
  • Getting the current time

사이드이펙트 자체를 나쁘게 보아서는 안된다. 실제로 종종 쓰이기 때문이다. 단 함수를 순수하게 선언하려면 어떤 것도 포함해서는 안된다. 모든 기능이 순수하거나 순수해야하는 것은 아니다.

왜 함수는 순수해야하나?

  1. 가독성

사이드이펙트로 인해서 읽기에 어려움이 따른다. 순수하지 않은 함수는 결정적이지 않으므로 주어진 입력에 대해 여러가지 다른 값을 반환할 수 있다.

우리는 다양한 가능성을 설명해야하는 코드를 작성하게 된다.

이 스니핏은 다양한 방법으로 실패할 수 있다. getTokenFromServer에 전달된 ID가 유효하지 않는 경우 어떻게 해야하나? 서버에 예상 토큰대신 충돌하여 오류를 반환한 경우 어떻게 해야하나

또한 순수함수는 문맥없이 읽기 쉽다. 필요한 모든 매개 변수를 미리 수신하고 응용프로그램의 상태와 대화 / 변조하지 않는다.

  1. 테스트 용이

순수함수는 본질적으로 결정론적이어서 단위 테스트를 작성하는 것이 휠씬 간단하다.

  1. 병렬 코드

순수함수는 입력에만 의존하고 부작용을 일으키지 않기 때문에 병렬 스레드가 실행되고 공유메모리를 사용하는 시나리오에 적합하다.

  1. 모듈성과 재사용성

순수함수를 작은 논리단위로 생각해야한다. 입력에만 의존하기 때문에 코드베이스의 다른 부분이나 다른 프로젝트 사이의 기능을 쉽게 재사용할 수 있다.

  1. 참조 투명성

간단히 말해서 참조 투명성은 프로그램의 전체 동작을 변경하지 않고 함수 호출을 출력값으로 대체할 수 있을을 의미한다.

순수함수는 많은 이점이 제공되지만 응용프로그램에 순수한 기능만 있는 것은 현실적이지 못한다.

그러나 우리의 응용프로그램에 적용하면 사이드이펙트가 없어서 외부세계에 관찰이 가능한 영향을 미치지 않는다. 대신 우리는 모든 부작용을 코드베이스의 특정부분에 캡슐화하려고 한다. 이렇게 하면 순수한 함수에 대한 단위 테스트를 작성하고 작동한다는 것을 알면 앱에서 문제가 발생하면 추적하기가 쉬워진다.

JavaScript에서 순수함수가 중요한 이유

함수형 프로그래밍에는 순수함수가 많이 사용된다. 또한 ReactJS 및 Redux와 같은 라이브러리에는 순수함수를 사용해야한다. 그러나 순수함수는 단일 프로그래밍 패러다임에 의존하지 않는 일반 Javascript에서도 사용할 수 있다. 순수한 기능과 불순한 기능을 혼합하여 사용할 수 있다.

모든 기능이 순수하거나 순수해야하는 것은 아니다. 예를 들어, DOM을 조작하는 버튼 누름에 대한 이벤트 핸들러는 순수한 기능에 적합지 않다. 그러나 이벤트 핸들러는 다른 순수한수를 호출하여 애플리케이션의 불완전한 함수를 줄일 수 있다.

테스트 가능성 및 리팩토링

가능한 경우 순수함수를 사용하는 또 다른 이유는 테스트와 리팩토링이다.

순수함수를 사용하면 얻을 수 있는 이점 중 하나는 즉시 테스트 할 수 있다는 것이다. 동일한 결과를 생성한다.

코드 유지 관리 및 리팩토링이 휠씬 쉬워지낟. 순수함수를 변경할 수 있으며 의동하지 않은 사이드이펙트를 변경할 수 있으며 의도하지 않는 부가용으로 인해 전체 응용프로그램이 엉망이 되어 지옥을 디버깅할 필요가 없다.

올바르게 사용하면 순수한 기능을 사용하면 더 나은 품질의 코드가 생성된다.

부록 - 관찰 가능한 사이트이펙트란?

관찰 가능한 사이드이펙트는 기능 내에서 외부 세계와의 상호작용이다. 함수 외부에 존재하는 변수를 변경하거나 함수 내에서 다른 메소드 호출까지 가능하다. 순수함수가 순수함수를 호출하는 경우 사이드이펙드가 아니며 호출한 함수는 여전히 순수하다.

Reference