React를 학습하다 보면 pure function이라는 개념을 자주 접하게 된다.
오늘은 pure function이 어떤 것을 의미하는지 깊게 파보도록 하겠다.
아래 블로그에 상세히 설명이 되어 있어 번역하여 옮겨본다.
Master the JavaScript Interview: What is a Pure Function?
Pure functions are essential for a variety of purposes, including functional programming, reliable concurrency, and React+Redux apps. But…
medium.com
What is a Pure Function?
순수 함수는 functional 프로그래밍, 안전한 동시 실행, React, Redux 등에 필수적으로 사용된다. 하지만 이 순수 함수라는 게 무슨 말일까?
순수 함수란:
- 같은 입력값에는 항상 같은 결과가 나와야 한다.
- side effect가 없어야 한다.
순수 함수에 대해 자세히 알아보기 전에 함수에 대해 좀 더 자세히 들여다보자. 다양한 시각에서 함수를 바라보면 함수 프로그래밍에 대해 더 쉽게 이해할 수 있을 것이다.
What is a Function?
함수란 argument라 불리는 인풋을 받아 return value라고 불리는 아웃풋을 제공하는 하나의 과정이다. 함수는 아래의 목적을 위해 사용된다.
- Mapping: 주어진 인풋에 기초대 아웃풋을 생산한다. 함수는 인풋 값을 아웃풋 값으로 연결(매핑)한다.
- Procedures: 함수는 일련의 과정들을 수행한다고 할 수 있다. procedure라고 알려진 일련의 절차와 프로그래밍 스타일을 procedural programming이라고도 한다.
- I/O: 어떤 함수는 스크린, 저장, 시스템 기록, 혹은 네트워크와 같이 다른 시스템의 일부와 소통하기 위해 존재하기도 한다.
Mapping
순수 함수에서 Mapping은 전부라고 할 수 있다. 함수는 인풋 argument를 return value와 매핑한다. 즉 각각의 인풋마다 아웃풋이 있다는 말이다. 함수는 인풋을 받아 그에 상응하는 아웃풋을 리턴한다.
Math.max() 메서드는 숫자 argument를 받아 가장 큰 숫자를 리턴한다.
Math.max(2, 8, 5); // 8
위 예에서 2,8,5는 argument이다. 이 값들은 함수로 전달되게 된다.
Math.max()는 어떠한 숫자든 argument로 받고, 그 값들 중 가장 큰 값을 반환하는 함수이다. 위의 경우 가장 큰 숫자로 넘겨진 수는 8이다.
함수는 컴퓨터 연산에 아주 중요한 역할을 차지한다. 함수는 사용 가능하게끔 데이터를 처리하는 역할을 한다. 우수한 개발자는 함수를 선언형으로 작성을 해 다른 사람이 코드를 보았을 때 이 코드가 어떤 역할을 하는지 쉽게 파악할 수 있도록 한다.
수학에서도 함수는 있다. 자바스크립트의 함수와 비슷하게 생겼는데, 대수학에서 본 적 있을 것이다. 아래와 같이 생겼다:
f(x) = 2x
위의 기호가 뜻하는 바는 f라고 하는 함수를 선언하고 x라고 하는 argument를 받고 이를 2로 곱한다는 뜻이다.
이 함수를 사용하기 위해서는 단순히 값을 f에 전달하기만 하면 된다:
f(2)
대수학에서 이것은 아래와 정확하게 같은 값이다.
4
따라서 f(2)는 4로 대체될 수 있다.
그렇다면 이제 함수를 자바스크립트로 변환해 보자.
const double = x => x * 2;
함수 결과를 console.log를 통해 확인할 수 있다.
console.log( double(5) ); // 10
위에서 f(2)를 4로 바꿀 수 있다고 한 부분을 기억하는가? 이 경우 자바스크립트 엔진도 마찬가지로 double(5)를 10으로 바꾸게 된다.
Pure Functions
console.log(double(5))는 console.log(10)과 같다.
이 것이 참인 이유는 double()은 순수 함수이기 때문이다. 하지만 만약 double()이 디스크에 값을 저장한다거나 콘솔에 기록을 하는 등 side-effect를 발생시킨다면 단순하게 그냥 double(5)를 10으로 바꿀 수는 없다.
참조 투명성을 원한다면, 순수 함수를 사용해야 한다.
필자는 당신이 순수 함수와 친해지기를 추천한다. 만약 프로그램을 만들기 위한 요건들 중 순수 함수를 사용하는게 실용적이라면 순수함수를 사용해야 한다. 순수 함수는 인풋을 받아 인풋을 기반으로 아웃풋을 리턴한다. 따라서 순수함수는 프로그램 코딩에서 가장 간단하게 재사용 가능한 건축 블록인 셈이다. 아마도 컴퓨터 공항에서 가장 중요한 디자인 원칙은 KISS(Keep it simple, stupid)일 것이다. 순수함수는 가능한 가장 stupid simple한 방식이다.
순수 함수는 아주 많은 이점들이 있다. 그리고 함수적 프로그램의 기반을 형성한다. 순수함수는 mutable한 상태와 관련있는 클래스 등 외부의 상태로부터 독립적이다. 이러한 독립적 성격은 다량의 CPU의 병렬 처리와 분산 연산 클러스터에서 사용될 수 있으며 이는 과학적이면서 자원이 많이 필요한 컴퓨터 업무에서 필수적인 요소이다.
순수함수는 또한 극도로 독립적이다. 옮기거나 리 펙터 링 그리고 코드를 재배열하기 쉽게 만들어 프로그램이 미래의 변화에 더욱 유연하고 확장성 있게 만들어 준다.
The Trouble with Shared State
수년 전 필자는 사용자들이 아티스트를 데이터베이스에서 찾고 그들의 음악을 웹 플레이어에 넣을 수 있는 애플리케이션을 제작 중이었다. 이때 구글에서 검색창에 키워드를 입력하면 결과를 바로 보여주는 구글 Instant가 탄생하게 되었다. AJAX를 활용한 자동완성 기능은 짧은 기간에 큰 유행이 되었다.
문제는 사용자가 API 자동완성 기능의 응답이 도착하는 시간보다 빠르게 입력을 하면 여러 이상한 버그를 발생시켰다. 이는 경쟁 상황을 발생시켜 새로운 자동완성이 이전의 자동완성에 의해 교체되게끔 했다.
어째서 이런 일이 발생했을까? 이유는 각각의 AJAX 상속 핸들러가 직접 유저에게 보이는 추천 리스트를 업데이트할 수 있었기 때문이다. 가장 느린 AJAX 요청은 맹목적으로 결과를 갱신시키면서 유저의 집중을 받을 수 있었다.
이 문제를 해결하기 위해, 필자는 쿼리 추천 상태를 단일 데이터에서 관리하는 추천 매니저를 만들었다. 추천 매니저는 현재 pending 상태의 AJAX 요청을 알고 있으며, 사용자가 새로운 입력을 할 때 새로운 요청이 발생하기 전에 pending 된 AJAX 요청을 삭제함으로써 단 하나의 응답 핸들러만이 UI 상태 업데이트를 할 수 있도록 했다.
어떠한 형태의 비동기적 오퍼레이션이나 동시성은 비슷한 경쟁 상태를 만들어 낼 수 있다. 경쟁 상태는 아웃풋이 제어할 수 없는 이벤트(네트워크, 장치 오류, 사용자 입력 등)의 결과에 의존할 때 발생한다. 사실 공유된 state를 사용하고 있고 그 state가 고의든 아니든 확정되지 않은 요소들에 매우 심하게 의존하고 있다면 결과를 예측하기란 불가능하다. 그리고 이것은 결과를 제대로 테스트하거나 이해하기가 불가능함을 의미한다. Scala를 만든 Martin Odersky가 말했듯이:
non-determinism = parallel processing + mutable state
프로그램 결정론은 컴퓨팅에서 바람직한 특성이다. 아마 당신은 자바스크립트가 싱글 스레드 언어라서 병렬 처리 문제로부터 안전하기 때문에 문제가 되지 않는다고 생각할지도 모른다. 하지만 AJAX 예를 보아 알 수 있듯이, 싱글 스레드 자바스크립트 엔진에 동시성이 없지는 않다. 오히려 자바스크립트에는 수많은 동시성이 존재한다. API I/O, event listeners, web workers, iframes, 그리고 timeout은 프로그램에 문제를 발생시킬 수 있으며 이를 공유된 state와 합치면 버그를 위한 레시피가 완성된다.
순수 함수는 이러한 버그를 피할 수 있도록 도와준다.
Given the Same Input, Always Return the Same Output
double() 함수 예제에서, 함수는 결과로 언제든지 치환 가능하고 프로그램은 항상 같은 값을 가지게 된다. 프로그램 내에서 double(5)는 문맥이나 호출 횟수와는 상관없이 항상 10과 같은 값을 가지게 된다.
하지만 모든 함수에서 항상 같은 결과를 기대할 수는 없다. 몇몇 함수들은 결과를 생성하는데 argument 이외의 다른 정보에 의존한다.
아래 예제를 보자.
Math.random(); // => 0.4011148700956255
Math.random(); // => 0.8533405303023756
Math.random(); // => 0.3550692005082965
어떠한 argument를 전달하지 않았지만 매번 다른 아웃풋을 생성하고 있으며, Math.random은 순수하지 않다는 의미가 된다.
Math.random()은 새로운 0과 1 사이의 랜덤 한 숫자를 매번 생성한다. 따라서 프로그램의 의미를 변경하지 않고서는 함수를 단순히 0.4011148700956255로 치환할 수는 없다.
만약 치환하게 된다면 같은 값 만들 계속 반환할 것이다. 컴퓨터에게 랜덤한 값을 요청한다는 것은 이전에 받은 값과는 다른 값을 기대하고 요청을 한 것일 텐데, 매번 같은 값만들 반환하는 주사위를 무엇하러 사용을 하겠는가?
Pure Functions Produce No Side Effects
순수 함수는 side effect가 없기 때문에 외부의 state를 변경시키지 않는다.
Immutability
자바스크립트의 객체 argument는 참조값들이다. 즉 만약 함수가 객체 혹은 배열 파라미터를 변형한다면 외부 함수의 state도 변형 시킬 수 있다. 순수 함수는 절대 외부의 state를 병형해서는 안된다.
'Random' 카테고리의 다른 글
드디어 모든 섹션이 끝났다! Section3 회고 (SEB 35) (0) | 2022.01.17 |
---|---|
[AWS] s3와 RDS의 차이점 (0) | 2022.01.06 |
Cookie와 그 특성들 (0) | 2021.12.29 |
Google Dinosaur 게임 해킹하기 (0) | 2021.12.26 |
페어 프로그래밍이란 무엇인가? (0) | 2021.10.05 |