함수형 프로그래밍
함수형 프로그래밍
프로그래밍 패러다임
명령형 프로그래밍(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 라이센스를 따릅니다.