CS study/java

함수형 프로그래밍 패러다임

블랑v 2024. 3. 24. 15:05

 

Lambda와 Stream을 다루면서, 이것을 잘 활용할 수 있는 함수형 프로그래밍의 개념도 같이 정리해보고자 한다.

 

https://csg1353.tistory.com/213

 

람다(Lambda)식의 정의와 함수형 인터페이스

https://www.youtube.com/watch?v=4ZtKiSvZNu4 먼저 해당 포스팅은 다음 유튜브 설명을 참조했음을 미리 밝힌다. 이해에 큰 도움이 되니 확인해보길 권장한다. 기초 개념 자바에서 람다 표현식과 스트림 API는

csg1353.tistory.com

 

https://csg1353.tistory.com/214

 

함수형 프로그래밍이 뭔가요?

사전적 개념

함수형 프로그래밍(Functional Programming, FP)은 프로그래밍 패러다임 중 하나로, 계산을 수학적 함수의 평가로 취급하고 상태 변경이나 가변 데이터를 피하는 것을 중심으로 하는 방식이다.

 

함수형 프로그래밍은 부수 효과(Side Effects)를 최소화하거나 제거하여 프로그램의 동작을 예측 가능하게 만드는 것을 목표로 한다. 이를 통해 코드의 테스트, 디버깅, 재사용성을 향상시킬 수 있다.

 

쉽게 이해할 수 있도록 설명하는 함수형 스트림

'계산을 수학적 함수의 평가로 취급하고 상태 변경이나 가변 데이터를 피하는 것을 중심으로 하는 방식' 은 언뜻 보면 이해하기 힘든 설명이다. 조금 더 베이스부터 설명하고자 한다.

 

함수형 프로그래밍의 두 가지 주요 개념은 순수 함수와 불변성이다.

 

이 두 개념을 통해 코드를 더 예측 가능하고, 재사용 가능하며, 테스트하기 쉽게 만들 수 있다는 것이다.

(= 유지보수!)

 

먼저, 이 용어의 개념을 읽어 보자.

  • 순수 함수(Pure functions): 동일한 입력에 대해 항상 동일한 출력을 반환하며, 외부 상태를 변경하지 않고 부수 효과가 없는 함수이다.
//예를 들어 이런 add 함수는 어떠한 변수를 넣어도, 같은 로직으로 동작할 것이다.
//이를 순수 함수라고 할 수 있다.
int add(int x, int y) {
    return x + y;
}

 

 

  • 불변성(Immutability): 데이터가 한 번 생성되면 변경할 수 없다. 데이터를 변경하고 싶은 경우, 변경된 데이터의 복사본을 생성하여 사용한다. (마치 static final.. 과 같다.)
List<String> addElementImmutable(List<String> list, String newElement) {
    List<String> newList = new ArrayList<>(list);
    newList.add(newElement);
    return newList;
}

 

위의 함수는 입력 파라미터의 'list'를 변경하지 않는다.

대신, 새 요소(newElement)를 추가한 새로운 리스트(newList)를 생성하여 반환한다.

 

이 방식은 기존 데이터(list)의 불변성을 유지하면서 데이터 변화를 처리하는 과정이다.

이 개념이 이해된다면, 장점을 다시 한번 살펴보자.

 

함수형 프로그래밍의 장점

  1. 예측 가능성
    순수 함수와 불변성으로 인해 코드의 동작을 예측하기 쉽다. 외부 상태에 의존하지 않으므로, 같은 입력에는 언제나 같은 출력이 나온다. (순수 함수는 변수가 없다.)
  2. 재사용성과 조합성
    순수 함수는 어떤 상황에서든 동일하게 동작하기 때문에, 코드의 재사용성이 높아지고, 여러 함수를 조합해도 예측 가능한 결과를 얻을 수 있다.
  3. 병렬 처리 용이성
    불변성으로 인해, 여러 스레드에서 동시에 데이터를 읽어도 문제가 없다.
    데이터가 변경되지 않기 때문에, 병렬 처리 시에 발생할 수 있는 데이터 충돌 문제를 피할 수 있다.

맨 처음의 사전정 정의를 다시 읽어보자.

함수형 프로그래밍은 부수 효과(Side Effects)를 최소화하거나 제거하여 프로그램의 동작을 예측 가능하게 만드는 것을 목표로 한다. 

 

즉, '예상할 수 있는 동작 제어'로 프로그램의 동작을 예측할 수 있고, 이를 통해 빠른 유지보수가 가능하게 하는 개념이다!

이를 위해 불변성과 순수 함수를 활용하는 것이다. 

 

그 외 함수형 프로그래밍의 주요 특징

  • 일급 함수(First-class functions): 함수를 일반 값처럼 변수에 할당하고, 다른 함수의 인자로 전달하거나, 함수로부터 반환할 수 있다.
  • 고차 함수(Higher-order functions): 함수를 인자로 받거나, 함수를 결과로 반환하는 함수이다.
  • 함수 합성(Function composition): 여러 함수를 결합하여 새로운 함수를 만드는 기법이다.
  • 레이지 평가(Lazy evaluation): 필요할 때까지 계산을 미루는 방식으로, 성능 최적화에 유용할 수 있다. (마치 Batch 처럼..)

 

그래서 람다와 스트림이 이것과 무슨 관계가 있나요?

Java에서 함수형 프로그래밍을 할 수 있도록 지원하는 개념이 람다와 스트림이다.

 

람다식과 스트림 API는 자바에서 함수형 프로그래밍의 개념을 구현하고 활용하는 데 중요한 역할을 한다.

람다식을 통해 메서드(함수)를 간결하게 표현하고, 스트림 API를 사용하여 데이터 컬렉션을 선언적으로 처리할 수 있게 된다.

 

이러한 접근 방식은 함수형 프로그래밍의 핵심 원리인 "데이터의 변형을 함수의 연속적인 적용을 통해 수행"하는 것을 자바에서 가능하게 한다.

 

람다식과 함수형 프로그래밍의 연관성

1. 함수를 일급 시민으로 취급

람다식은 자바에서 함수(메서드)를 마치 변수처럼 다룰 수 있게 한다.

 

// 기존 로직
List<String> fruits = Arrays.asList("사과", "바나나", "체리"); 
for(String fruit : fruits) {
    System.out.println(fruit);
}

//람다식을 통해 함수를 객체처럼 사용
List<String> fruits = Arrays.asList("사과", "바나나", "체리");
fruits.forEach(fruit -> System.out.println(fruit));

 

이는 함수형 프로그래밍에서 말하는 '함수를 일급 시민(first-class citizen)으로 취급'하는 개념과 일치한다.

즉, 함수를 다른 함수의 인자로 전달하거나, 함수에서 함수를 반환하고, 변수에 함수를 할당할 수 있게 하는 주요 개념이다.

 

2. 선언적 프로그래밍 스타일

스트림 API는 데이터 컬렉션의 처리를 위한 선언적 접근 방식을 제공한다.

 

/*
filter(score -> score >= 90)는 "점수(score)가 90 이상인 것만 선택"하는 함수이다. 
데이터(성적 리스트)를 변경하지 않고, 새로운 데이터(90점 이상의 성적 리스트)를 만들어낼 수 있다. (highScores)
*/
List<Integer> scores = Arrays.asList(95, 80, 75, 98, 65);
List<Integer> highScores = scores.stream() // 스트림 생성
                                  .filter(score -> score >= 90) // 90점 이상 필터링
                                  .collect(Collectors.toList()); // 리스트로 수집
System.out.println(highScores);

 

개발자는 "무엇(What)을 하고 싶은지"만을 표현하고, "어떻게(How) 할 것인지"에 대한 구체적인 구현은 라이브러리에 맡긴다. 이는 함수형 프로그래밍의 선언적 특성을 반영하는 예시라고 할 수 있다.