Functional Programming(FP)의 개념과 특징

정찬·2025년 9월 1일

Functional Programming

목록 보기
1/4
post-thumbnail

함수형 프로그래밍이란?

함수형 프로그래밍은 프로그래밍 패러다임의 한 종류입니다.

명령형, 객체지향 패러다임과는 다르게 상태를 바꾸지 않고 함수 합성을 통해 문제를 푼다는 점이 핵심입니다.

상태를 바꾸지 않는다는 의미가 와닿지 않으니, 다른 프로그래밍 패러다임의 예시와 함께 함수형 프로그래밍의 특징을 확인해봅시다.

1. 명령형 프로그래밍

대표적인 언어로는 C, Python이 있습니다.

nums = [1, 2, 3, 4, 5]
evens = []
for n in nums:
    if n % 2 == 0:
        evens.append(n * n)

위 파이썬 코드는 배열의 상태를 직접 변경하며, 세부적인 처리 과정을 개발자가 모두 제어합니다.

2. 절차적 프로그래밍

절차적 프로그래밍은 명령형 프로그래밍의 하위 개념입니다.

큰 문제를 절차 단위로 나눠서 해결한다는 특징이 있습니다.

대표 언어로는 C, Pascal이 있습니다.

int square_even(int n) {
    if (n % 2 == 0) return n * n;
    return -1;
}

절차를 만들어 코드를 모듈화했지만, 여전히 상태 변경 중심입니다.

3. 객체지향 프로그래밍

객체지향 프로그래밍은 캡슐화, 상속, 다형성을 통해 복잡성을 관리합니다.

대표 언어로는 Java, C++, Python이 있습니다.

class Number {
    int value;
    Number(int v) { this.value = v; }
    boolean isEven() { return value % 2 == 0; }
    int square() { return value * value; }
}

데이터를 객체로 감싸 상태와 행위를 함께 관리하지만, 여전히 상태 변화를 기반으로 동작합니다.

4. 함수형 프로그래밍

상태를 변경하지 않고 순수 함수와 함수 합성으로 문제를 해결합니다.

데이터는 불변하고 선언적 스타일로 무엇을 할지만 표현합니다.

대표 언어로는 Haskell, Scala, Clojure이 있으며 JavaScript, Python, Java Stream에서 부분적으로 지원합니다.

List<Integer> evens = nums.stream()
                          .filter(n -> n % 2 == 0)
                          .map(n -> n * n)
                          .toList();

상태를 변경하지 않고 짝수를 골라 제곱한다 라는 의도만 표현합니다.

상태와 부작용이 줄어 코드가 예측 가능해지고, 데이터 흐름이 명확해져 병렬 처리나 테스트에도 유리하다는 특징이 있습니다.

프로그래밍 패러다임은 서로 경쟁하는 개념이 아니라, 각각의 장단점을 가지고 있고 상황에 따라 보완적으로 쓰일 수 있는 도구 에 가깝습니다.

함수형 프로그래밍 역시 마찬가지로, 명령형·절차적·객체지향 패러다임과 함께 실무에서 공존하고 있습니다.

함수형 프로그래밍의 특징

함수형 프로그래밍은 상태 변화와 부작용을 최소화하고, 함수 중심으로 문제를 해결하는 패러다임입니다.

함수형 프로그래밍의 주요 특징을 알아봅시다.

예제 코드는 익숙한 Java로 작성되었습니다

1. 순수 함수 (Pure Function)

예제 코드

int square(int n) {
    return n * n; // 입력 n에 대해 항상 같은 결과
}

순수 함수의 특징

  • 같은 입력이면 항상 같은 출력을 반환합니다.
  • 외부 상태를 변경하지 않으며 부작용이 없습니다.

2. 불변성 (Immutability)

예제 코드

List<Integer> nums = List.of(1, 2, 3); // 불변 리스트

불변성의 특징

  • 데이터를 한 번 정의하면 변경하지 않고, 새로운 값을 생성해서 사용합니다.
  • 상태를 직접 바꾸지 않기 때문에 버그 발생 가능성 감소 및 병렬 처리에 유리합니다.

Q1. 왜 상태를 직접 바꾸지 않는다는 특징이 병렬 처리에 유리할까?

여러 스레드가 같은 데이터를 동시에 읽고 쓸 때, 동기화 문제가 발생할 수 있습니다.

불변 객체는 상태가 변하지 않기 때문에, 락이나 동기화 처리 없이 여러 스레드가 동시에 접근 가능합니다.

따라서, 상태를 직접 바꾸지 않는다는 특징은 병렬 처리에서 Race Condition을 자연스럽게 방지할 수 있습니다.

매 번 새로운 값을 생성했을 때의 Side Effect

불변성을 유지하면 이전 상태를 유지할 수 있어 디버깅과 테스트가 쉬워지고, 데이터 변환이 안전하다는 장점이 있습니다.

하지만 매 번 새로운 값을 생성하므로 메모리 사용량이 증가한다는 단점이 있습니다.

그럼에도 불구하고 함수형 프로그래밍이 사용되는 이유는, 현대 JVM과 GC는 이런 패턴에 최적화되어 있어, 성능 문제가 크지 않는 경우가 많습니다.

3. 고차 함수 (Higher-Order Function)

함수형 프로그래밍에서 함수는 1급 객체로 취급됩니다.

그리고 함수형 프로그래밍에서 사용되는 함수는 모두 순수 함수만을 사용합니다.

즉, 함수를 변수에 담거나, 인자로 전달하거나, 반환값으로 사용할 수 있는 값처럼 다룰 수 있습니다.

1급 객체란?

1급 객체란 다음과 같은 특징을 가진 객체를 의미합니다.

  • 변수나 데이터 구조 안에 저장할 수 있다.
  • 파라미터로 전달할 수 있다.
  • 반환값으로 반환될 수 있다.
  • 할당에 사용된 이름과 무관하게 고유한 구별이 가능하다.

함수형 프로그래밍에서 위와 같은 특징 때문에 고차 함수라는 특징이 가능해집니다.

고차 함수는 함수를 인자로 받거나, 함수를 반환하는 함수를 말합니다.

예제 코드

// 함수 자체를 변수에 담기 (1급 객체)
Function<Integer, Integer> square = x -> x * x;

// 함수에 전달 (고차 함수)
List<Integer> squared = nums.stream().map(square).toList();

코드를 부연 설명하자면 square 는 함수를 값처럼 다루는 1급 객체입니다.

map 은 이 1급 객체를 받아 각 리스트 요소에 적용하는 고차 함수입니다.

고차 함수의 특징

  • 함수를 인자로 전달하거나 반환값으로 사용할 수 있습니다.
  • 함수를 값처럼 다루어 유연한 조합과 재사용이 용이합니다.

4. 함수 합성 (Function Composition)

함수 합성은 작은 단위의 함수를 결합해 새로운 함수를 만드는 것을 의미합니다.

FP에서는 복잡한 로직도 여러 순수 함수를 조합해 쉽게 구현할 수 있습니다.

예제 코드

Function<Integer, Integer> add2 = x -> x + 2;

Function<Integer, Integer> squareThenAdd2 = add2.compose(square);

함수 합성의 특징

  • 작은 단위 함수를 재사용하고 조합하여 복잡한 로직을 처리합니다.
  • 함수 실행 순서를 직관적으로 이해할 수 있어 절차를 파악하기 쉽습니다.

5. 선언적 스타일 (Declarative Style)

선언적 스타일은 무엇을 할지(What)만 정의하고, 어떻게 할지(How)는 숨기는 방식입니다.

FP에서는 데이터 처리와 로직을 선언적으로 표현할 수 있어 코드가 간결하고 읽기 쉽습니다.

예제 코드

List<Integer> evens = nums.stream()
                          .filter(n -> n % 2 == 0)
                          .map(n -> n * n)
                          .toList();

선언적 스타일의 특징

  • "짝수만 골라 제곱한다"와 같이 의도를 명확히 표현합니다.
  • 내부 반복, 상태 변경 등의 세부 절차는 숨겨져 가독성이 높습니다.
  • 코드가 간결하고 데이터 흐름이 명확해 유지보수와 테스트가 용이합니다.

6. 지연 평가 (Lazy Evaluation)

지연 평가는 필요할 때까지 계산을 미루는 방식입니다.

이를 통해 불필요한 연산을 줄이고, 무한 시퀀스 처리도 가능합니다.

예제 코드

-- Haskell 예시: 무한 리스트
take 5 [1..] -- [1,2,3,4,5]

지연 평가의 특징

  • 실제로 값이 필요할 때만 계산하여 성능 최적화에 유리합니다.
  • 무한 시퀀스나 큰 데이터를 다룰 때도 안전하게 처리할 수 있습니다.

마치며

함수형 프로그래밍은 코드의 가독성과 재사용성을 높이고, 부작용을 줄이며 병렬 처리에도 안전한 장점이 있습니다.

하지만 학습 곡선이 비교적 가파르고, 기존 명령형·객체지향 중심 사고에서 벗어나야 하기 때문에 처음에는 낯설 수 있습니다.

제가 함수형 프로그래밍을 배우려는 가장 큰 이유는, 기존의 명령형이나 객체지향 방식으로 작성한 코드가 복잡하고 가독성이 떨어지는 경우를 자주 느꼈기 때문입니다.

그렇기 때문에 데이터 흐름이 보다 명확하고, 재사용이 용이한 코드를 작성할 수 있는 방법을 찾고자 함수형 프로그래밍을 공부하고 있으며, 코딩테스트나 다른 개발 활동을 하면서 점차 적용해보려고 합니다.

다음 시간에는 Java에서 함수형 프로그래밍을 활용하는 방법과 Stream API, 람다, 불변 컬렉션 등을 포스팅하는 주제로 돌아오겠습니다!

0개의 댓글