멍두의 개발새발

[함수형 프로그래밍] - 기원, 주목을 받는 이유, 장점과 단점, 그리고 활용 예시 본문

Programming

[함수형 프로그래밍] - 기원, 주목을 받는 이유, 장점과 단점, 그리고 활용 예시

멍두 2025. 3. 10. 10:59
반응형

이 글에서는 함수형 프로그래밍 구현을 어떻게 하는지에 대해 알아보기 보단,

함수형 프로그래밍의 기원, 주목을 받는 이유, 장점과 단점, 그리고 구체적으로 어떤 경우에 사용하면 이점을 얻을 수 있는지에 대해서 작성해보고자 한다. 

 

 


함수형 프로그래밍의 기원

(굳이 알필요는 없지만 재밋자나요)

1930년대 수학자 알론소 처치가 개발한 람다 대수

  • 함수의 정의, 함수 적용, 귀납적 함수 추상화, 수학 연산을 표현하는 형식 체계
  • 한마디로 함수를 간단하게 표현한 것

을 프로그래밍에 도입한 것이다. 

x는 x다 x → x
x를 입력 받으면 x의 제곱을 반환 x → x*x
x, y를 입력 받으면 x 제곱과 y 제곱의 합 반환 (x, y) → x * x + y * y

 


함수형 프로그래밍이 주목 받는 이유

객체지향 프로그래밍은 기존 절차지향적 프로그래밍의 문제점들을 해결했지만 여전히 몇 가지 한계가 존재한다.

 

첫번째로 함수의 비일관성인데,

객체지향 프로그래밍에서 메서드는 내부의 상태에 의존할 때가 많다. 따라서 같은 입력에 같은 결과 값을 반환하지 않을 수도 있다. 게다가 시스템이 복잡해질수록 객체 간의 의존도가 높아지며 이는 테스트를 더욱 어렵게 만들고, 예상치 못한 예외를 발생시킬 가능성이 있다.

 

두번째로는 객체 내 상태 변화 제어의 어려움이 있다.

객체 내 상태가 변화가능하다면 이에 따른 프로그래밍의 동작을 이해하고 추론하기 어려워진다. 특히 멀티스레딩 환경에서 동시에 상태를 변경하려고 접근한다면 동기화 문제가 발생한다. 

 

결국 내가 개발한 프로그램이 내가 생각한대로 동작하지 않을 수도 있다 라는 불확실성이 큰 문제점이다.

 

이에 대한 한가지 해결책으로 함수형 프로그래밍이 주목받는다. 함수형 프로그래밍은 상태를 최소화하고, 동일한 입력에 항상 같은 출력을 내는 순수 함수를 기반으로 한다.

 

함수형 프로그래밍의 중요한 개념이자 장점 두가지가 바로 순수 함수와 불변성이다.

말만 들으면 감이 잘 오지않으니 쉽게 풀어서 얘기하자면

 

1. 순수함수

  • 수행 결과를 입력값에만 의존하도록 설계하여 항상 동일한 결과를 반환한다.
  • 시스템의 불규칙한 상태에 대한 영향 최소화한다. 이에 예측이 가능하다.
  • 이러한 특성으로 테스트, 디버깅이 용이하다
  • 객체 내 상태 변화 제어의 어려움이 근본적으로 발생하지 않는다.(상태가 없응께) 즉, 멀티스레드환경에서도 동기화 문제 없이 안전하게 실행 된다.

2. 불변성

  • 한 번 생성된 데이터는 변경되지 않는다.
  • 수정이 필요한 경우 새로운 데이터를 생성한다.
  • 불변성을 유지하면 예상치 못한 부작용을 방지할 수 있다.

장점

결국 함수형 프로그래밍의 가장 큰 장점은 확실성과 예측가능함이라고 할 수 있겠다.

내 코드가 어떤 환경에서든 같은 결과를 내놓는다는 확신을 가질 수 있는 것이다. 이는 특히 분산 시스템이 많아진 현대 소프트웨어 환경에  더욱더 중요해지고 있다. 자연스럽게 테스트가 쉬워지고 유지보수가 용이해지는 코드가 되는 것 또한 중요한 이점 중 하나다.

 

단점

단순함이 장점이지만, 복잡한 현실세계를 표현할 때 단순함을 강요하는 측면이 있다. 객체지향프로그래밍에서는 자연스럽게 표현되는 상태 변화가 함수형에서는 구현하는 것이 직관성이 떨어지고, 같은 기능을 구현하려면 다양한 함수의 조합을 고민해봐야하는 경우가 생길 수도 있다.

이 점이 결국 함수형 프로그래밍이 익숙치 않은 다른 개발자들이 코드를 이해하고 설계하는데 장벽이 될 수 있다.

 


어떤 상황에 사용하면 이점을 얻을 수 있을까? 

직관적인 데이터 처리를 하고 싶을 때!

고차함수 : 하나 이상의 함수를 인자로 받고, 함수를 반환하는 것을 의미한다. (ex 자바에서 스트림)

함수형 프로그래밍은 선언형이라서 데이터 처리할 때 어떻게 처리할지는 나의 관심사가 아니다. 무엇을 할지만 정하면 되어 직관적으로 복잡한 로직을 간단하게 표현할 수 있다. (스트림은 정보가 많아서 생략하겠음)

 

람다가 중복되는 코드 안에 있을 때!

일급 함수 : 프로그래밍 언어에서 함수를 first-class citizen으로 취급하면 함수를 값으로 사용할 수 있다. 변수에 할당하거나, 인자로 전달하거나 반환값으로 사용할 수 있다.

 

ex) 중복 코드 제거

    void name2() {
        List<String> foods = List.of("pizza", "pasta", "burger", "iceCream");

        List<Integer> fiveLengthFoods = foods.stream()
                .filter(str -> str.length() != 5)
                .map(String::length)
                .toList();

        List<Integer> notFiveLengthFoods = foods.stream()
                .filter(str -> str.length() != 5)
                .map(String::length)
                .toList();
    }

 

현재 foods에서 길이가 5글자인 애들과, 5글자 아닌 애들을 filter하고, length를 가져오는 코드이다. 여기서 !=, ==만 다르고 나머지 요소는 전부 동일하다.

이럴때 predicate를 인자로 받는 filter함수를 만든다면 나머지 요소들을 재활용할 수 있을 것이다.

    void test() {
        List<String> foods = List.of("pizza", "pasta", "burger", "iceCream");

        List<Integer> fiveLengthFoods = filter(foods, isLengthFive());
        List<Integer> notFiveLengthFoods = filter(foods, isLengthNotFive());
    }

    List<Integer> filter(List<String> list, Predicate<String> predicate) {
        return list.stream()
                .filter(predicate)
                .map(String::length)
                .toList();
    }

 

이런식으로 함수를 인자로 넘겨서 코드를 재활용할 수 있다.

 

객체지향 프로그래밍과 함수형 프로그래밍은 상호 배타적인 개념이 아니다. 현대적인 프로그래밍 패러다임에서는 두 가지 방식을 조화롭게 활용하는 것이 중요하다.  특히 액션(실행시점이나 횟수에 의존하는 행동)에서 계산을 최대한 분리하여 같은 입력에 같은 출력을 내놓을 수 있는 코드를 많이 작성하려고 노력하는 것이 중요하다. 

반응형