함수형 프로그래밍 (Functional Programming)

수학적 함수의 개념을 바탕으로 한 프로그래밍 패러다임.
이 방식은 상태 변경과 데이터 변경을 최소화하고 함수의 응용을 강조.

특징

  1. 순수 함수: 동일한 입력에 대해 항상 같은 출력을 반환하며, 부수 효과가 없습니다.
  2. 불변성: 데이터는 생성된 후 변경되지 않습니다.
  3. 고차 함수: 함수를 인자로 받거나 함수를 반환할 수 있습니다.
  4. 재귀: 반복문 대신 재귀를 사용하여 문제를 해결합니다.
  5. 지연 평가: 필요한 시점까지 계산을 미룹니다.

장점

  1. 코드의 간결성과 가독성: 함수 중심의 코드로 더 읽기 쉽고 이해하기 쉽습니다.
  2. 테스트와 디버깅 용이성: 순수 함수는 예측 가능하므로 테스트하기 쉽습니다.
  3. 병렬 처리 용이성: 불변성과 부수 효과 없음으로 인해 동시성 처리가 쉽습니다.
  4. 모듈성과 재사용성: 작은 순수 함수들의 조합으로 큰 프로그램을 만들 수 있습니다.

단점

  1. 학습 곡선: 전통적인 명령형 프로그래밍과 다른 사고방식이 필요합니다.
  2. 성능 이슈: 불변성으로 인해 메모리 사용량이 증가할 수 있습니다.
  3. 복잡성: 일부 문제에서는 함수형 접근이 더 복잡할 수 있습니다.

주의사항 및 고려사항

  1. 적절한 사용: 모든 문제에 함수형 접근이 최선은 아닙니다. 문제의 특성을 고려해야 합니다.
  2. 성능 최적화: 불변성과 순수 함수로 인한 성능 저하를 주의해야 합니다.
  3. 팀의 이해도: 팀 전체가 함수형 프로그래밍 개념을 이해하고 있어야 합니다.

예시

Python

Python 예제는 금융 거래 분석 시스템을 구현.
다음과 같은 함수형 프로그래밍 개념들을 보여준다:

  1. 불변 데이터 구조 (dataclass with frozen=True)
  2. 순수 함수들 (filter_by_category, calculate_total 등)
  3. 고차 함수 (compose)
  4. 함수형 파이프라인
  5. map, filter, reduce 활용
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from functools import reduce
from typing import List, Callable, Any
from datetime import datetime
from dataclasses import dataclass
from copy import deepcopy

# 불변 데이터 클래스 정의
@dataclass(frozen=True)
class Transaction:
    id: str
    amount: float
    timestamp: datetime
    category: str

# 순수 함수들
def filter_by_category(transactions: List[Transaction], category: str) -> List[Transaction]:
    """특정 카테고리의 거래만 필터링하는 순수 함수"""
    return list(filter(lambda t: t.category == category, transactions))

def calculate_total(transactions: List[Transaction]) -> float:
    """거래 금액의 총합을 계산하는 순수 함수"""
    return reduce(lambda acc, t: acc + t.amount, transactions, 0.0)

def map_to_amounts(transactions: List[Transaction]) -> List[float]:
    """거래 목록에서 금액만 추출하는 순수 함수"""
    return list(map(lambda t: t.amount, transactions))

def compose(*functions: List[Callable]) -> Callable:
    """함수들을 조합하는 고차 함수"""
    def composition(x: Any) -> Any:
        result = x
        for f in reversed(functions):
            result = f(result)
        return result
    return composition

# 거래 분석을 위한 함수형 파이프라인
def analyze_transactions(transactions: List[Transaction]) -> dict:
    """거래 데이터를 분석하는 함수형 파이프라인"""
    
    # 카테고리별 총액 계산
    categories = set(map(lambda t: t.category, transactions))
    category_totals = {
        category: compose(
            lambda t: calculate_total(t),
            lambda t: filter_by_category(t, category)
        )(transactions)
        for category in categories
    }
    
    # 가장 큰 거래 금액 찾기
    max_transaction = max(transactions, key=lambda t: t.amount)
    
    # 평균 거래 금액 계산
    average_amount = calculate_total(transactions) / len(transactions)
    
    return {
        "category_totals": category_totals,
        "max_transaction": max_transaction,
        "average_amount": average_amount
    }

# 예시 데이터와 실행
def main():
    # 샘플 거래 데이터 생성
    transactions = [
        Transaction("1", 100.0, datetime.now(), "food"),
        Transaction("2", 500.0, datetime.now(), "rent"),
        Transaction("3", 50.0, datetime.now(), "food"),
        Transaction("4", 1000.0, datetime.now(), "salary"),
    ]
    
    # 분석 실행
    results = analyze_transactions(transactions)
    
    # 결과 출력
    print("카테고리별 총액:", results["category_totals"])
    print("최대 거래:", f"{results['max_transaction'].amount} ({results['max_transaction'].category})")
    print("평균 거래 금액:", results["average_amount"])

if __name__ == "__main__":
    main()

Javascript

Node.js 예제는 작업 관리 시스템을 구현
다음 개념들을 보여준다:

  1. 불변 객체 (Object.freeze 사용)
  2. 함수 합성 (compose, pipe)
  3. 순수 함수를 통한 데이터 처리
  4. 클로저를 활용한 상태 관리
  5. 불변성을 유지하면서 상태 업데이트
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
// 불변 데이터를 위한 클래스
class Task {
    constructor(id, title, status, priority, dueDate) {
        Object.freeze({
            id,
            title,
            status,
            priority,
            dueDate: new Date(dueDate)
        });
    }
}

// 순수 함수들
const filterByStatus = (tasks, status) =>
    tasks.filter(task => task.status === status);

const sortByPriority = tasks =>
    [...tasks].sort((a, b) => b.priority - a.priority);

const filterByDueDate = (tasks, date) =>
    tasks.filter(task => task.dueDate.toDateString() === date.toDateString());

const groupByStatus = tasks =>
    tasks.reduce((acc, task) => ({
        acc,
        [task.status]: [(acc[task.status] || []), task]
    }), {});

// 고차 함수: 함수 조합을 위한 유틸리티
const compose = (fns) =>
    initialValue =>
        fns.reduceRight((value, fn) => fn(value), initialValue);

const pipe = (fns) =>
    initialValue =>
        fns.reduce((value, fn) => fn(value), initialValue);

// 작업 관리를 위한 함수형 파이프라인
const createTaskManager = (initialTasks = []) => {
    // 불변성을 위해 깊은 복사 수행
    const tasks = [initialTasks];

    const getHighPriorityTasks = () =>
        pipe(
            filterByStatus,
            sortByPriority
        )(tasks, 'pending');

    const getTodaysTasks = () =>
        pipe(
            tasks => filterByDueDate(tasks, new Date()),
            sortByPriority
        )(tasks);

    const getTasksSummary = () => ({
        totalTasks: tasks.length,
        statusGroups: groupByStatus(tasks),
        highPriorityCount: getHighPriorityTasks().length,
        todaysTasks: getTodaysTasks()
    });

    // 새로운 작업 추가 (불변성 유지)
    const addTask = (taskData) => {
        const newTask = new Task(
            taskData.id,
            taskData.title,
            taskData.status,
            taskData.priority,
            taskData.dueDate
        );
        return createTaskManager([tasks, newTask]);
    };

    return {
        getHighPriorityTasks,
        getTodaysTasks,
        getTasksSummary,
        addTask
    };
};

// 사용 예시
const main = () => {
    const initialTasks = [
        new Task(1, "기능 구현", "pending", 3, "2024-12-01"),
        new Task(2, "버그 수정", "in_progress", 5, "2024-12-01"),
        new Task(3, "문서 작성", "completed", 2, "2024-12-02"),
        new Task(4, "코드 리뷰", "pending", 4, "2024-12-01")
    ];

    const taskManager = createTaskManager(initialTasks);
    
    console.log("작업 요약:", taskManager.getTasksSummary());
    console.log("높은 우선순위 작업:", taskManager.getHighPriorityTasks());
    console.log("오늘의 작업:", taskManager.getTodaysTasks());
    
    // 새 작업 추가 (불변성 유지)
    const updatedManager = taskManager.addTask({
        id: 5,
        title: "테스트 작성",
        status: "pending",
        priority: 4,
        dueDate: "2024-12-01"
    });
    
    console.log("업데이트된 작업 요약:", updatedManager.getTasksSummary());
};

main();

용어 정리

용어설명

참고 및 출처