Functional Programming 시각적으로 이해하기
각 개념의 아키텍처를 애니메이션 다이어그램으로 살펴보세요. 카드를 클릭하면 더 깊은 내용을 확인할 수 있습니다.
순수 함수
입력만으로 결과가 결정되는 함수
순수 함수는 말 그대로 입력만 보고 답을 내는 함수입니다. 같은 값을 넣으면 언제 다시 불러도 같은 결과가 나오고, 호출했다고 해서 바깥세상이 달라지지도 않습니다. 전역 변수, 파일, 네트워크, 현재 시각 같은 외부 조건을 몰래 끌어오지 않는다는 뜻입니다. 그래서 순수 함수는 계산 로직을 가장 믿기 쉬운 단위로 만들어 줍니다. 입력과 출력만 보면 되니 코드가 왜 이런 값을 내는지 설명하기도, 조합하기도 훨씬 수월해집니다.
불변성
값을 바꾸지 않고 새 값을 만드는 원칙
불변성은 한 번 만든 값을 그 자리에서 바꾸지 않는다는 약속입니다. 값을 수정하고 싶으면 원본을 덮어쓰는 대신, 바뀐 내용을 반영한 새 값을 만들어 돌려줍니다. 원본이 그대로 남아 있으니 같은 데이터를 여러 곳에서 함께 봐도 서로 발을 밟지 않습니다. 어느 시점에 어떤 값이었는지 추적하기 쉬워지고, 순수 함수도 훨씬 지키기 쉬워집니다.
일급 함수
함수를 값처럼 다루는 언어의 성질
일급 함수는 언어가 함수를 숫자나 문자열 같은 '평범한 값'으로 취급한다는 뜻입니다. 그래서 함수를 변수에 담고, 다른 함수에 넘기고, 반환값으로 돌려받고, 배열이나 객체 안에 넣어 둘 수 있습니다. 포인트는 문법이 멋지다는 게 아니라, 동작 자체를 데이터처럼 주고받을 수 있다는 데 있습니다. 이 성질이 있어야 고차 함수, 콜백, 함수 합성 같은 함수형 패턴이 자연스럽게 성립합니다.
고차 함수
함수를 받거나 돌려주는 함수
고차 함수는 함수를 인자로 받거나, 함수를 결과로 돌려주는 함수입니다. 일반 함수가 숫자나 문자열 같은 값을 다룬다면, 고차 함수는 '동작' 자체를 입력받거나 새 동작을 만들어 냅니다. 그래서 공통 뼈대는 한곳에 두고, 안에서 실제로 무엇을 할지는 바깥에서 갈아 끼울 수 있습니다. 반복되는 구조는 줄이고, 달라지는 부분만 또렷하게 드러내는 데 좋은 도구입니다.
클로저
함수가 바깥 스코프의 값을 기억하는 구조
클로저는 함수가 만들어질 때 주변 스코프까지 함께 기억하는 구조입니다. 그래서 함수 안에서 바깥 변수를 참조하고 있으면, 바깥 함수 호출이 이미 끝난 뒤에도 그 값에 계속 접근할 수 있습니다. 쉽게 말해 '함수 코드'만 돌아다니는 게 아니라, 그 함수가 태어날 때 붙들고 있던 환경도 같이 따라다니는 셈입니다. 자바스크립트처럼 함수와 스코프가 자연스럽게 결합된 언어에서는 별도 문법 없이도 계속 마주치게 됩니다.
함수 합성
작은 함수를 이어 붙여 큰 함수 만들기
함수 합성은 작은 함수 여러 개를 순서대로 이어 붙여 하나의 큰 함수처럼 쓰는 방식입니다. 앞 단계의 출력이 다음 단계의 입력이 되면서 데이터가 파이프라인처럼 흘러갑니다. 이렇게 쪼개 두면 각 단계는 따로 테스트하고 재사용할 수 있고, 전체 함수는 '무엇을 어떤 순서로 처리하는가'를 코드 표면에 그대로 보여 줍니다.
map/filter/reduce
컬렉션을 변환하는 세 가지 기본 도구
`map`, `filter`, `reduce`는 컬렉션을 다룰 때 제일 자주 손에 잡히는 세 가지 기본 도구입니다. `map`은 각 원소를 다른 값으로 바꾸고, `filter`는 조건에 맞는 것만 남기고, `reduce`는 여러 원소를 하나의 값으로 모읍니다. 셋 다 원본을 건드리지 않고 새 결과를 만든다는 공통점이 있고, 고차 함수가 실무에서 어떻게 쓰이는지 가장 선명하게 보여 주는 사례이기도 합니다.
재귀
함수가 자기 자신을 호출해 반복을 표현
재귀는 함수가 자기 자신을 다시 부르면서 반복 작업을 표현하는 방식입니다. 큰 문제를 같은 모양의 더 작은 문제로 줄여 가고, 충분히 작아졌을 때는 바로 답을 내는 종료 조건에서 멈춥니다. 반복문이 '몇 번 돌까'를 세는 쪽에 가깝다면, 재귀는 '이 문제를 어떻게 더 작은 같은 문제로 바꿀까'를 생각하게 만듭니다. 트리처럼 깊이를 미리 알 수 없는 구조에서 특히 자연스럽습니다.
커링
다인자 함수를 단항 함수 체인으로 바꾸기
커링은 여러 인자를 한꺼번에 받는 함수를, 인자 하나씩 받는 함수들의 연쇄로 바꾸는 기법입니다. `f(a, b, c)`를 `f(a)(b)(c)`처럼 읽게 만드는 방식이라고 보면 됩니다. 이렇게 바꾸면 함수가 매 단계마다 입력 하나만 받게 되어, 함수 합성이나 고차 함수가 기대하는 단항 함수 스타일에 훨씬 잘 맞습니다. 결국 여러 인자 함수의 모양을 조합하기 좋은 형태로 다시 잡아 주는 작업입니다.
부분 적용
인자 일부를 미리 채워 전용 함수 만들기
부분 적용은 함수의 인자 몇 개를 먼저 채워 두고, 나머지 인자만 받는 새 함수를 만드는 기법입니다. `(locale, currency, amount)`를 받는 함수를 `(amount)`만 받는 통화 포맷터로 줄이는 식이 대표적입니다. 핵심은 함수를 완전히 다른 존재로 바꾸는 게 아니라, 자주 반복되는 공통 인자를 앞에서 고정해 호출부를 더 짧고 목적에 맞게 만드는 데 있습니다.
지연 평가
값이 필요할 때까지 계산을 미루는 전략
지연 평가는 값을 선언한 순간 바로 계산하지 않고, 정말 그 값이 필요해질 때까지 계산을 미루는 전략입니다. 표현식은 먼저 '이걸 어떻게 계산할지'만 들고 있다가, 소비 지점이 생기면 그때 비로소 실행됩니다. 덕분에 무한 시퀀스처럼 끝이 없는 값도 다룰 수 있고, 전체 중 일부만 필요할 때는 나머지 계산을 통째로 건너뛸 수 있습니다. 함수형 프로그래밍에서 계산 시점 자체를 설계 대상으로 보는 대표적인 감각입니다.
Maybe/Option
없을 수 있는 값을 드러내는 합 타입
Maybe/Option은 값이 있을 수도 있고 없을 수도 있다는 사실을 타입으로 드러내는 합 타입입니다. 값이 있으면 `Some`이나 `Just`, 값이 없으면 `None`이나 `Nothing`처럼 두 경우만 허용합니다. 중요한 점은 '없음'을 숨겨진 예외나 어중간한 `null`로 흘리지 않고, 정상적으로 다뤄야 할 값의 한 형태로 끌어올린다는 것입니다. 그래서 호출자는 이 값이 비어 있을 가능성을 코드에서 바로 읽게 됩니다.
Either/Result
성공과 실패를 모두 값으로 반환하는 타입
Either/Result는 계산 결과가 성공일 수도, 실패일 수도 있다는 사실을 값으로 표현하는 합 타입입니다. 성공이면 `Ok`나 `Right`, 실패면 `Err`나 `Left`처럼 두 경우 중 하나만 가집니다. 핵심은 실패를 예외로 밖에 던져 버리지 않고, 정상적인 반환값의 일부로 붙들어 둔다는 데 있습니다. 그래서 함수 시그니처만 봐도 '이 함수는 실패할 수 있구나, 실패하면 이런 정보가 오겠구나'가 드러납니다.
ADT
값의 가능한 모양을 닫아 두는 타입 모델
ADT(Algebraic Data Type)는 값이 가질 수 있는 모양을 몇 가지 변형으로 닫아 두고, 각 변형에 필요한 데이터만 정확히 붙여 두는 모델링 방식입니다. 여기서 algebraic이라는 말은 합(sum)과 곱(product)이라는 두 조합 방식에서 옵니다. 곱 타입은 여러 필드가 함께 존재하는 구조이고, 합 타입은 여러 변형 중 정확히 하나만 선택되는 구조입니다. ADT는 이 둘을 조합해 '이 값이 가질 수 있는 상태는 여기까지'를 타입으로 못 박아 줍니다.
패턴 매칭
값의 모양을 보고 분기하며 내부 값을 꺼내는 방식
패턴 매칭은 값의 실제 모양을 보고 분기하면서, 그 안에 들어 있는 데이터까지 한 번에 꺼내 쓰는 방식입니다. 단순히 `if (tag === "success")`처럼 조건만 검사하는 데서 끝나지 않고, `Success data`라면 그 자리에서 `data`를 바인딩해 바로 다음 계산으로 넘길 수 있습니다. 함수형 프로그래밍에서는 값을 ADT로 닫아 두고, 패턴 매칭으로 그 값을 소비하는 조합이 정말 자주 반복됩니다. 그래서 패턴 매칭은 단순 제어문이라기보다 데이터 모델과 맞물린 분기 도구에 가깝습니다.