Conceptly
← 전체 목록
λ

Pure Function

기반 원칙입력만으로 결과가 결정되는 함수

순수 함수는 말 그대로 입력만 보고 답을 내는 함수입니다. 같은 값을 넣으면 언제 다시 불러도 같은 결과가 나오고, 호출했다고 해서 바깥세상이 달라지지도 않습니다. 전역 변수, 파일, 네트워크, 현재 시각 같은 외부 조건을 몰래 끌어오지 않는다는 뜻입니다. 그래서 순수 함수는 계산 로직을 가장 믿기 쉬운 단위로 만들어 줍니다. 입력과 출력만 보면 되니 코드가 왜 이런 값을 내는지 설명하기도, 조합하기도 훨씬 수월해집니다.

아키텍처 다이어그램

🔍 구조 다이어그램

Diagram preview

The interactive diagram loads after the page becomes ready.

점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다

왜 필요한가요?

함수 하나를 이해하려고 들어갔는데 전역 상태를 읽고, 데이터베이스를 건드리고, 캐시를 갱신하고, 로그까지 남기고 있으면 실제 결과가 무엇 때문에 바뀌는지 금방 흐려집니다. 같은 인자로 두 번 호출했는데도 결과가 다르면 버그가 함수 내부에 있는지, 바깥 환경이 바뀐 탓인지부터 헷갈립니다. 테스트를 하려 해도 매번 환경을 세팅하고 치워야 해서 검증 비용이 커집니다. 계산 로직을 믿을 수 있는 단위로 다루려면, 최소한 결과가 입력 외의 것에 흔들리지 않는다는 보장이 필요합니다.

왜 이런 방식이 등장했나요?

전역 상태를 바꾸면서 작업을 진행하는 명령형 스타일은 시스템이 작을 때는 큰 문제가 되지 않았습니다. 그러나 시스템이 커지고 스레드가 여러 개 돌아가기 시작하면서, 같은 변수를 여러 곳에서 수정하는 코드가 예측 불가능한 버그의 온상이 됐습니다. 동시성 문제, 디버깅 난이도, 테스트 어려움이 누적되자 업계는 상태 변경을 최소화하는 스타일에 주목하기 시작했습니다. Haskell, Clojure, Scala 같은 언어가 순수 함수를 중심 원리로 삼았고, JavaScript나 Python 같은 범용 언어에서도 React의 함수형 컴포넌트나 Redux 리듀서처럼 순수 함수를 적극적으로 도입하는 흐름이 생겼습니다. 순수 함수가 다시 주목받은 것은 취향의 문제가 아니라 큰 시스템을 안전하게 다루는 현실적인 전략이었기 때문입니다.

내부적으로 어떻게 동작하나요?

순수 함수의 내부를 들여다보면 두 가지 규칙만 지킵니다. 첫째, 결과는 오직 인자에만 의존합니다. 함수 밖의 변수, 현재 시각, 랜덤 값, 파일 내용을 읽지 않습니다. 둘째, 함수 바깥에 영향을 남기지 않습니다. 전역 변수를 바꾸지 않고, 콘솔에 출력하지 않으며, 네트워크 요청도 보내지 않습니다. 이 두 규칙이 지켜지면 참조 투명성이라는 핵심 성질이 따라옵니다. add(2, 3)을 코드 어디에서 호출해도 결과는 5이므로, 이 호출을 그냥 5로 바꿔도 프로그램 의미가 달라지지 않습니다. 이것이 순수 함수가 추론하기 쉽고 테스트하기 쉬운 근본 이유입니다. 함수의 동작을 이해하려면 그 함수의 시그니처(입력과 출력 타입)만 보면 됩니다.

코드로 보면

순수 함수 vs 불순 함수

// 순수 함수
function add(a, b) {
  return a + b;
}

// 불순 함수 — 외부 상태에 의존
let counter = 0;
function increment() {
  counter += 1;
  return counter;
}

// 불순 함수 — 외부 상태를 변경
function addItem(list, item) {
  list.push(item);  // 인자로 받은 배열을 수정
  return list;
}

// 순수 버전 — 새 배열을 반환
function addItemPure(list, item) {
  return [...list, item];
}

add는 인자만으로 결과가 정해지지만, increment는 외부 counter에 의존하고 이를 변경합니다. addItem은 인자로 받은 배열을 직접 수정해 호출자에게 영향을 주는데, addItemPure는 원본을 건드리지 않고 새 배열을 만들어 돌려줍니다.

경계와 구분

순수 함수와 불순 함수의 차이를 묻는 질문은 단순한 용어 구분이 아니라 프로그램을 어떻게 쪼개서 추론할지의 문제입니다. 순수 함수는 입력과 출력만으로 동작을 완전히 설명할 수 있어 테스트와 병렬화가 쉽고, 불순 함수는 외부 세계와 상호작용해야 하는 부분(파일 쓰기, 네트워크 요청, 시간 조회)을 실제로 수행합니다. 둘 중 어느 것이 '더 좋다'기보다는, 프로그램의 핵심 계산 로직은 순수 함수로 유지하고 외부 효과가 필요한 부분을 바깥쪽으로 몰아내는 설계 전략이 일반적입니다. 순수 함수로 계산하고 불순 영역에서 그 결과를 파일이나 DB에 쓰는 구조가 추론하기 쉽고 변경에도 강합니다.

트레이드오프

Gain 함수를 입력과 출력만으로 완전히 이해할 수 있어 테스트 작성, 디버깅, 리팩토링이 크게 쉬워지고 동시성 문제도 근본적으로 줄어듭니다. Cost 실제 애플리케이션은 어딘가에서 파일을 읽고, 네트워크를 호출하고, 상태를 저장해야 하므로 순수성을 유지하려면 효과가 있는 코드를 경계 영역으로 밀어내는 추가 설계 작업이 필요합니다. 단순한 작업도 '새 값을 만들어 반환'하는 스타일로 바꾸면 초기 코드량이 늘어납니다. Decision Scale 비즈니스 규칙이나 데이터 변환 로직처럼 재사용되고 자주 테스트되는 코드는 순수 함수로 유지할 가치가 큽니다. 반대로 한 번 쓰고 버리는 스크립트나 입출력이 본질인 얇은 래퍼 코드는 굳이 순수성을 고집할 필요가 없습니다.

언제 쓰나요?

순수 함수는 React 렌더링 로직, Redux 리듀서, 금액 계산, 데이터 변환, 검증 규칙처럼 '같은 입력이면 같은 답이 나와야 하는' 코드에 특히 잘 맞습니다. 이런 코드는 mock이나 stub 없이 값만 넣어도 테스트가 되고, 문제가 생겨도 함수 바깥까지 뒤지지 않아도 돼서 원인 추적이 빨라집니다. 실무에서는 핵심 계산을 순수 함수로 모아 두고, 데이터베이스 저장이나 API 호출 같은 효과는 가장 바깥 얇은 층으로 밀어내는 식으로 경계를 나누는 경우가 많습니다.

비즈니스 로직 핵심단위 테스트병렬 처리캐시와 메모이제이션