포스트

함수형 프로그래밍

함수형 프로그래밍

프로그래밍 패러다임

명령형 프로그래밍(imperative)

  • 핵심 개념: 어떻게 할 것인지 구체적으로 명령을 내리는 방식
  • 특징
    • 프로그램이 어떤 순서와 단게로 동작해야 하는지를 구체적인 제어 흐름(조건문, 반복문 등)으로 기술
    • 변수의 값이 바뀌면서 상태가 변해감
    • CPU의 동작 방식과 유사하여, 전통적인 하드웨어와의 직관적인 일치
  • 장단점
    • 장점: 컴퓨터의 동작 방식과 매우 유사해 이해하기 직관적, 제어 흐름을 상세히 제어하기 쉬움
    • 단점: 프로그램 규모가 커지면 상태 변경에 따른 복잡도가 증가

절차지향 프로그래밍(Procedural Programming)

  • 핵심 개념: 명령형 프로그래밍의 대표적인 형태로, 프로그램을 절차(Procedure)나 함수(Function) 단위로 나누어 순서대로 실행
  • 특징
    • 명령형 패러다임이 하위 개념으로 볼 수 있음
    • 공통된 로직을 재사용하기 위해 함수나 프로시저를 만들어 사용
    • 데이터와 절차가 분리되어 있다 라는 말로도 자주 설명 됨
  • 장단점
    • 장점: 구조적 프로그래밍 기법(모듈화, 함수화)으로 코드 가독성 상승, 코드 재사용성 향상
    • 단점: 데이터와 로직이 명확히 분리되지 않을 때, 코드 유지 보수가 어렵고 대형 프로젝트에서 복잡성 증가

객체지향 프로그래밍(Object-Oriented Programming)

  • 핵심 개념: 프로그램을 객체라는 추상화된 단위로 구성. 각 객체는 상태(필드, 속성)와 행동(메서드)을 갖고 있으며, 메시지 교환(메서드 호출)을 통해 상호작용
  • 특징
    • 캡슐화, 추상화, 상속, 다형성 과 같은 특징이 있음
    • 데이터와 해당 데이터를 처리하는 함수를 하나의 객체로 묶어서 관리해 유지보수성과 확장성을 높임
  • 장단점
    • 장점: 객체라는 단위로 묶이므로 코드 재사용성, 확장성, 유지보수성 우수. 대규모 시스템 설계에 적합
    • 단점: 과도한 객체 분리나 복잡한 상속 구조 등으로 인해 오히려 복잡도가 증가할 수 있음

선언형 프로그래밍(Declarative Programming)

  • 핵심 개념: 무엇을 할 것인지를 기술하고 어떻게 구현, 실행될지는 위임하는 방식
  • 특징
    • 구체적인 제어 흐름(조건문, 반복문 등)을 직접 작성하기 보다, 원하는 결과나 조건을 선언적으로 표현
    • 상태 변화보다는 결과에 초점을 맞추어 코드를 작성
    • 함수형 프로그래밍 등이 선언형 패러다임에 속하거나 밀접하게 ㄱ돤련됨
  • 장단점
    • 장점: 구현의 복잡한 로직을 많이 숨길 수 있어, 높은 수준에서 문제 해결에 집중가능. 비즈니스 로직을 직관적으로 표현하기 쉬움
    • 단점: 언어나 환경이 제공하는 추상화 수준에 의존적이며, 내부 동작이 보이지 않을 경우 디버깅이 어려울 수 있음. 낮은 수준의 최적화나 세밀한 제어가 필요한 상황에서는 제약이 생길 수도 있음

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

  • 핵심 개념: 무엇을 할 것인지를 수학적 함수(Function)들로 구성하고, 부수효과(side Effect) 최소화 및 불변성(Immutable State)을 강조하는 프로그래밍 방식
  • 특징
    • 선언형 접근에 가까움: 어떻게가 아니라, 어떤 결과를 원한다고 선언
    • 순수 함수를 중시: 같은 입력이 주어지면 항상 같은 출력
    • 데이터는 불면 하게 처리: 재할당 대신 새로운 데이터를 만들어 반환
    • 함수가 일급 시민으로 취급: 고차 함수, 함수를 인자로 넘기거나 반환 가능
  • 장단점
    • 장점: 상태 변화가 없거나 최소화되므로 디버깅과 테스트 용이, 병렬 처리 및 동시성 처리가 간단해지는 경향
    • 단점: 명령형 사고방식에 익숙한 프로그래머에게는 초기 접근이 어려울 수 있음, 계산 과정에서의 메모리 사용이 증가할 수 있음

함수형 프로그래밍의 핵심 개념과 특징

순수 함수(Pure Function)

  • 같은 인자를 주면 항상 같은 결과를 반환하는 함수
  • 외부 상태(변ㄴ할 수 있는 전역 변수 등)에 의존하거나, 외부 상태를 변경하는 부수 효과(Side Effect)가 없는 함수를 의미

부수 효과(Side Effect) 최소화

  • 함수형 프로그래밍에서는 상태 변화를 최소화하기 위해 변수나 객체를 변경하는 것을 지양
  • 이로 인해 프로그램의 버그가 줄어들고, 테스트나 병렬 처리, 동시성 지원이 용이해 짐

불변성(Immutable State) 지향

  • 데이터는 생성된 후 가능한 한 병경하지 않고, 변경이 필요한 경우 새로운 값을 생성해서 사용
  • 가변 데이터 구조에서 발생할 수 있는 오류를 줄이고, 프로그램 예측 가능성을 높여 줌
  • 불변성은 데이터를 변경하지 않기 때문에 부수 효과와도 관련이 있음

일급 시민

  • 함수가 일반 값(숫자, 문자열, 객체(자료구조) 등)과 동일한 지위를 가짐
  • 함수를 변수에 대입하거나, 다른 함수의 인자로 전달하거나, 함수에서 함수를 반환하는 고차 함수를 자유롭게 사용할 수 있음

선언형(Declarative) 접근

  • 어떻게가 아닌 무엇을 계산할지 기술
  • 복잡한 제어 구조나 상태 관리를 함수의 합성과 함수 호출로 대체하여 간결하고 가독성 높은 코드를 작성

함수 합성(Cimposition)

  • 간단한 함수를 조합해 더 복잡한 함수를 만드는 것을 권장
  • 작은 단위의 함수들을 체이닝(Chaining)하거나 파이프라이닝(Pipelining)해서 재사용성을 높이고, 코드 이해도를 높임
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CompositionMain1 {

  public static void main(String[] args) {
    // 1. x를 입력받아 x * x 하는 함수
    Function<Integer, Integer> square = x -> x * x;
    // 2. x를 입력받아 x + 1 하는 함수
    Function<Integer, Integer> add = x -> x + 1;

    // 함수 합성
    // 1. compose()를 사용한 새로운 함수 생성
    // 먼저 add 적용 후 square 적용하는 새로운 함수 newFunc1 생성
    // square(add(2)) -> square(3) -> 9
    Function<Integer, Integer> newFunc1 = square.compose(add);
    System.out.println("newFunc1 결과: " + newFunc1.apply(2));

    // 2. andThen()을 사용한 새로운 함수 생성
    // 먼저 square 적용 후 add 적용하는 새로운 함수 newFunc2 생성
    // add(square(2)) -> add(4) -> 5
    Function<Integer, Integer> newFunc2 = square.andThen(add);
    System.out.println("newFunc2 결과: " + newFunc2.apply(2));
  }
}

지연 평가(Lazy Evaluation)

  • 필요한 시점까지 계산을 미루는 평가 전략
  • 불필요한 계산 비용을 줄임

참고

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.