함수형 프로그래밍 패러다임 (Functional Programming Paradigms)
함수형 프로그래밍 패러다임이 상태 변화보다 값 변환과 합성을 중심에 두는 방식을 정리한 학습 노드입니다.
Article
M
Me
hyunyoun's Blog
programming-languages-compilersprogramming-languagescompilerslanguage-theorytype-systemsfunctional-programming-paradigmslanguages-compilerslearning9 min read
1. Overview
함수형 프로그래밍 패러다임(Functional Programming Paradigms)은 부작용(Side Effect) 없는 순수 함수(Pure Function), 불변 데이터(Immutable Data), 함수를 일급 시민(First-Class Citizen)으로 취급하는 프로그래밍 패러다임으로, 코드의 예측 가능성과 테스트 가능성을 높입니다.
학습자는 람다 대수(Lambda Calculus)의 핵심인 λx.x+1 표현식이 현대 언어의 람다/클로저(Closure)와 어떻게 연결되는지 살펴봅니다. 나아가 map, filter, fold/reduce 같은 고차 함수(Higher-Order Function)의 구성 가능성(Composability), 커링(Currying)과 부분 적용(Partial Application)의 함수 팩토리 패턴, **모나드(Monad)**가 순수 함수형 세계에서 I/O와 부작용을 다루는 방식, 그리고 순수 함수형 데이터 구조의 영구불변성(Persistence)까지 분석하여 Haskell, Scala, Elixir, 현대 JavaScript/Python의 함수형 스타일을 이해하는 역량을 확보합니다.
2. Scope & Boundaries
In-Scope
- 람다 대수 기초 (Lambda Calculus): α-변환, β-환원, η-변환, 자유 변수/묶인 변수, 커링(Currying).
- 고차 함수 (Higher-Order Functions):
map,filter,reduce/fold, 함수 합성(compose,.), 부분 적용(Partial Application),functools.partial. - 클로저와 커링 (Closure & Currying): 클로저의 환경 캡처, 커링된 함수 팩토리, 데이터 캡슐화.
- 모나드 (Monad): Maybe/Option 모나드, IO 모나드,
flatMap/>>=, 모나드 법칙. - 불변 데이터 구조 (Persistent/Immutable Data): 구조 공유(Structural Sharing), Persistent List/Tree.
Out-of-Scope
- Haskell 전체 언어 스펙: Typeclass, Functor, Applicative의 범주론(Category Theory) 심화 → 전문 FP 이론.
- 리액티브 프로그래밍(RxJS/Reactor): 비동기 스트림 합성 → 05-01-04 Concurrency Models 영역.
Boundaries
- FP vs OOP 트레이드오프: FP는 데이터와 함수를 분리하여 함수 합성으로 복잡도를 제어합니다. OOP는 데이터와 동작을 캡슐화하여 은닉(Encapsulation)으로 복잡도를 제어합니다. 부작용(네트워크, DB, UI)이 많은 대규모 시스템은 OOP의 캡슐화가, 데이터 변환 파이프라인(ETL, 데이터 처리)은 FP의 함수 합성이 더 자연스럽습니다.
3. Counterexample
- 부작용 있는 "순수 함수"의 숨은 상태 참조 (Hidden Side Effect): "이 함수는 순수해(Pure)요. 입력만 사용하고 출력만 반환해요"라는 주장의 코드
def get_total(items): global tax_rate; return sum(i.price for i in items) * tax_rate.tax_rate라는 전역 변수를 참조하므로, 다른 코드가tax_rate를 변경하면 같은items입력에 다른 결과를 냅니다. 이는 순수 함수(Pure Function, 같은 입력 → 항상 같은 출력, 전역 상태 참조 없음)의 정의를 위반하며, 단위 테스트가 실행 순서에 따라 통과/실패하는 불확실성을 만듭니다. - Fold/Reduce의 결합성 오류 (Fold Associativity Mistake):
fold_right(λa b → a - b, 0, [1,2,3,4])vsfold_left(λa b → a - b, 0, [1,2,3,4]). Fold Right:1-(2-(3-(4-0))) = 1-2+3-4 = -2. Fold Left:((((0-1)-2)-3)-4) = -10. 뺄셈(비결합적 연산)에서 fold의 방향(Left vs Right)이 결과를 완전히 바꿉니다.
4. Prerequisites
- 함수 개념 (Basic): 함수가 값을 받아 값을 반환한다는 수학적 정의를 이해해야 합니다.
- 재귀 (Basic): FP의 반복 대안이 재귀이므로 재귀 사고 필수. (04-03-01 Recursion)
5. Learning Map
6. Learning Topics
Basic
Core Topic 01: 부작용 없는 수학 함수와 람다의 기원, 순수 함수와 람다 대수 (Pure Functions & Lambda Calculus)
- Why to Learn: 함수형 프로그래밍의 "순수성(Purity)"이 단순한 스타일 선택이 아니라 "함수 = 수학적 함수(동일 입력 → 동일 출력, 부작용 없음)"라는 계약임을 이해하고, 이 계약이 병렬 처리, 테스트, 캐싱을 어떻게 안전하게 만드는지 파악하기 위해서입니다.
- What to Learn:
- Concepts: 순수 함수(Pure Function), 참조 투명성(Referential Transparency), 람다 대수(λx.e), β-환원(Beta Reduction), 자유 변수/묶인 변수.
- Skills: 사이드 이펙트(전역 변수 참조, I/O, 상태 변경) 식별, 순수/비순수 함수 구분.
- How to Learn:
- 1단계:
f(x) = x + 1순수 함수.f(5)=6, 항상. 반면g(x) = x + counter(전역counter참조)는counter가 변하면g(5)가 달라지는 비순수 함수. 순수 함수만으로 구성된 표현식은 어디서든 해당 값으로 대체 가능(참조 투명성)함을 확인합니다. - 2단계: 람다 대수 β-환원:
(λx. x+1) 5 = [x←5] x+1 = 5+1 = 6. 고차 함수:(λf. λx. f(f(x))) (λx. x+1) 3 = (λx. x+1)((λx. x+1) 3) = (λx. x+1)(4) = 5. 함수를 인자로 전달하는 FP의 핵심을 살펴봅니다.
- 1단계:
- Implement: 파이썬 순수 함수 스타일 리팩토링. 전역 상태 참조 함수
add_tax(price)→ 세율을 인자로 받는add_tax(price, rate)순수 변환.functools.lru_cache로 순수 함수를 자동 메모이제이션(같은 입력 → 항상 같은 출력이라 캐싱 안전)하는 성능 최적화 데모.
Recommended
Core Topic 02: 데이터 파이프라인의 레고 블록, 고차 함수와 합성 (Higher-Order Functions)
- Why to Learn: Pandas의
df.groupby().agg(), Java Stream의.filter().map().collect(), JavaScript Array의.filter().map().reduce()모두 고차 함수 합성 패턴이며, 이 패턴으로 복잡한 데이터 변환을 선언적으로(How가 아닌 What) 표현하는 역량을 갖추기 위해서입니다. - What to Learn:
- Concepts: 고차 함수(Higher-Order Function, 함수를 인자/반환값으로 받는 함수),
map,filter,reduce/fold, 함수 합성(Function Composition,(f∘g)(x) = f(g(x))). - Skills: 파이프라인 구성(
data | filter | map | reduce),functools.reduce, Python 리스트 컴프리헨션 vs 함수형 스타일 비교.
- Concepts: 고차 함수(Higher-Order Function, 함수를 인자/반환값으로 받는 함수),
- How to Learn:
- 1단계: 주문 목록에서 할인가 계산:
orders.filter(is_valid).map(calculate_price).reduce(sum). 각 함수가 하나의 책임만 가지는 FP 파이프라인의 선언적 스타일을 분석합니다. - 2단계: 함수 합성
compose(f, g)(x) = f(g(x)).double = lambda x: x*2,add_one = lambda x: x+1,double_then_add_one = compose(add_one, double).double_then_add_one(5) = 11. 합성으로 복잡한 변환을 조각 함수 조합으로 표현하는 방식을 살펴봅니다.
- 1단계: 주문 목록에서 할인가 계산:
- Implement: 파이썬
compose(*fns)유틸리티.compose(f, g, h)(x) = f(g(h(x))). 주문 데이터 파이프라인:process = compose(calculate_total, apply_discount, filter_valid).[{price:100, valid:True}, {price:200, valid:False}](./{price:100,-valid:true},-{price:200,-valid:false})처리 후 결과 검증. 명령형 루프 버전 vs FP 파이프라인 버전 코드 줄 수/가독성 비교.
Practical
Core Topic 03: 함수가 환경을 삼키다, 클로저와 커링 (Closures & Currying)
- Why to Learn: JavaScript의 즉각 실행 함수(IIFE), React의
useState훅, Python의 데코레이터, Redux의 미들웨어가 모두 클로저와 커링 패턴의 응용임을 이해하기 위해서입니다. - What to Learn:
- Concepts: 클로저(Closure, 자유 변수를 캡처하는 함수), 환경(Environment = 변수 바인딩 테이블), 커링(Currying,
f(x,y) → f(x)(y)), 부분 적용(Partial Application, 인자 일부만 고정). - Skills:
functools.partial, 커링된 함수 팩토리, 클로저로 private 상태 캡슐화.
- Concepts: 클로저(Closure, 자유 변수를 캡처하는 함수), 환경(Environment = 변수 바인딩 테이블), 커링(Currying,
- How to Learn:
- 1단계: 클로저:
def make_adder(n): return lambda x: x + n.add5 = make_adder(5).add5(3)→8.n=5라는 외부 변수를 람다가 캡처(Capture)하여make_adder가 이미 종료된 후에도n에 접근 가능한 클로저 동작을 분석합니다. - 2단계: 커링:
add(x, y) → add_curried(x)(y).add_curried(1)(2) = 3.add_curried(1)→ 새 함수lambda y: 1+y반환(부분 적용).double = add_curried(0) + ...대신multiply = lambda base: (lambda x: base * x)팩토리 패턴을 살펴봅니다.
- 1단계: 클로저:
- Implement: 파이썬
curry(f)함수.@curry데코레이터로def add(x, y, z): return x+y+z→add(1)(2)(3)및add(1, 2)(3)모두 작동.make_counter()클로저: 호출마다 count 증가하는 private 상태 캡슐화.counter = make_counter(); counter(); counter(); counter()→[1,2,3]출력.
Advanced
Core Topic 04: 부작용을 감싸는 수학적 컨테이너, 모나드와 불변 데이터 (Monad & Immutable Data)
- Why to Learn: 순수 함수형 언어(Haskell)에서 I/O, null 처리, 오류 전파를 순수성을 깨지 않고 처리하는 모나드의 철학과, 함수형 데이터 구조가 기존 데이터를 복사 없이 O(log n)에 "업데이트"하는 구조 공유 방식을 이해하기 위해서입니다.
- What to Learn:
- Concepts: 모나드(Monad,
return과>>=연산), Maybe 모나드(None 전파), IO 모나드(순수 코드와 I/O 분리), 모나드 법칙(Monad Laws). - Skills:
flatMap체인으로 null 체크 없이 안전한 연산 체인, 영구불변 리스트/트리 구조 공유.
- Concepts: 모나드(Monad,
- How to Learn:
- 1단계: Maybe 모나드:
safe_divide(10, 0)→Nothing.safe_log(-1)→Nothing. 체인:safe_divide(10, 2).flatMap(safe_log).flatMap(round). 중간에 Nothing이 발생하면 전체가 Nothing으로 단락(Short-circuit). null 체크 없는 안전한 체인 방식을 분석합니다. - 2단계: 영구불변 리스트 구조 공유:
list1 = [1,2,3,4]에cons(0, list1)→list2 = [0,1,2,3,4]. 내부적으로list2의 tail이list1을 공유(동일 메모리 포인터)하여 복사 없이 O(1) cons 연산이 가능한 구조 공유 방식을 살펴봅니다.
- 1단계: Maybe 모나드:
- Implement: 파이썬
Maybe모나드 클래스.class Just(Generic[T]),class Nothing.bind(f) -> Maybe[S]: Just면 f 적용, Nothing이면 Nothing 반환.Just(10).bind(lambda x: Just(x/2)).bind(lambda x: Nothing() if x<0 else Just(x)).bind(str)체인 출력. 영구불변 링크드 리스트PList.cons(val, tail)구조 공유 증명(is연산자로 동일 메모리 확인).
7. Terminology
8. References
Primary
- [P1] CS2023 - Programming Languages (PL) - Functional Programming
- [P5] SFIA - Software Development (PROG) - Declarative Paradigms
Secondary
- [Structure and Interpretation of Computer Programs (SICP)] Abelson & Sussman - Functional Abstraction
- [Category Theory for Programmers] Bartosz Milewski - Monads and Functors
Industry
- [Haskell Wiki] - Pure functions, Monads, and Lazy Evaluation
- [React Documentation] - Functional Components and Hooks (Immutability)
9. Final Checklist
Primary
- 상태 변경(Mutation)과 부수 효과(Side-effect)가 멀티스레드 환경에서 유발하는 데이터 레이스(Data Race)를 순수 함수가 어떻게 줄이는지 설명할 수 있는가?
-
map,filter,reduce와 같은 고차 함수(Higher-Order Function)를 사용하여 절차지향적for루프를 선언적 데이터 파이프라인으로 리팩터링할 수 있는가?
Secondary
- 참조 투명성(Referential Transparency)이 컴파일러의 메모이제이션(Memoization) 캐싱 최적화에 미치는 영향을 설명할 수 있는가?
- 불변 데이터 구조(Immutable Data Structures)가 값을 변경할 때 딥 카피(Deep Copy) 대신 구조적 공유(Structural Sharing)를 사용하는 방식을 설명할 수 있는가?
Industry
- 프론트엔드 React나 백엔드 비동기 처리에서 모나드(Monad) 철학(예:
Promise,Optional,Result)이 예외 처리를 파이프라인 체이닝으로 어떻게 다루는지 논증할 수 있는가? - 지연 평가(Lazy Evaluation)를 통해 무한 리스트(Infinite Stream)를 다룰 때 발생하는 메모리 지연 할당의 이점과, 반대로 썽크(Thunk) 누적으로 인한 메모리 릭 위험을 설계 관점에서 저울질할 수 있는가?