함수형 프로그래밍
프로그래밍 패러다임
함수형 프로그래밍에 대해서 알아보기 전 프로그래밍 패러다임을 간단히 알아보려고 한다.
프로그래밍 패러다임(Programming Paradigm)에서의 프로그래밍과 패러다임의 의미를 각각 살펴보자.
프로그래밍 : 컴퓨터 프로그램을 작성하는 일
패러다임 : 한 시대의 사람들의 견해나 사고를 근본적으로 규정하고 있는 인식의 체계
이러한 의미로 바라보았을 때, 프로그래밍 패러다임은 프로그래머가 컴퓨터 프로그램을 작성하는 데 있어 바라보는 관점 정도로 해석할 수 있을 것 같다.
즉, 프로그래머에게 프로그래밍의 관점을 갖게 하고 코드를 어떻게 작성할 지 결정하는 역할을 한다.
그렇다면, 현대에 주를 이루던 프로그래밍 패러다임에 대해서 몇 가지 살펴보자.
- 명령형 프로그래밍 : 어떻게(HOW) 할 것인가? (프로그래밍의 상태, 상태를 변경시키는 구문의 관점에서 연산을 설명하는 방식)
- 절차지향 프로그래밍 : 수행되어야 할 순차적인 처리 과정을 포함하는 방식 (e.g. C, C++)
- 객체지향 프로그래밍 : 객체들의 집합으로 프로그램의 상호작용을 표현 (e.g. C++, JAVA)
- 선언형 프로그래밍 : 무엇(WHAT)을 할 것인가? (어떤 방법으로 해야하는지를 나타내기보다, 무엇과 같은지를 설명하는 방식)
- 함수형 프로그래밍 : 순수 함수를 조합하고 소프트웨어를 만드는 방식
최근 많은 언어들이 객체 지향 프로그래밍을 넘어 함수형 프로그래밍을 도입하고 있으며 관심 또한 나날이 높아지는 모습을 보여주고있다.
절차지향 프로그래밍과 객체지향 프로그래밍은 C, C++, JAVA를 통해 많이 접해봤지만, 함수형 프로그래밍은 조금 생소한듯싶다.
하지만, 함수형 프로그래밍은 우리가 JAVA를 사용할 때 이미 많이 접하고 있는 부분 중 하나이다.
바로 다음과 같이 Stream API를 사용할 때이다.
ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.stream().forEach(System.out::println);
처음에 볼때는 이게 왜 함수형 프로그래밍이지? 라는 의무닝 들수도 있고 함수형 프로그래밍이 뭔지 잘 이해가 가지 않는다.
우리가 함수형 프로그래밍이라고 할 수 있는, 함수형 프로그래밍이 가지고 있는 특징에 대해서 자세히 살펴보자.
함수형 프로그래밍
함수형 프로그래밍에 대한 자세한 이해를 위해 함수형 프로그래밍의 특징에 대해서 살펴보자.
순수 함수(Pure Funtion)
순수 함수란 동일 입력 시 동일 출력을 보장하며, 부수 효과가 없다는 것을 의미한다.
여기서 말하는 부수 효과(Side-Effect)는 변수의 값을 변경하고 객체의 필드값을 설정하는 등 변화를 의미한다.
간단한 예를 살펴보자.
public class FunctionalProgramming {
static int num = 5;
public static void main(String[] args) {
System.out.println(add(1)); // 6
num = 10;
System.out.println(add(1)); // 11
}
static int add(int a) {
return a + num;
}
}
- 함수 add는 num에 의존적이다. (외부에 영향을 받는다.)
- num이 변경됨에 따라, 동일한 입력에 따라 동일 출력을 보장하지 않는다. 그에 따라 순수함수라고 바라볼 수 없다.
다음은 순수함수의 예이다.
public class FunctionalProgramming {
public static void main(String[] args) {
System.out.println(add(1, 3));
System.out.println(add(1, 3));
}
public static int add(int a, int b) {
return a + b;
}
}
- 함수 add는 어느 것에도 의존적이지 않다. (외부에 영향을 받지 않는다.)
- 동일한 입력에 따라 동일 출력을 보장한다.
함수형 프로그래밍은 다음과 같이 동일 입력에 대해 동일 출력을 보장하는 순수 함수를 사용하여야 한다.
불변성(Immutable)
불변성은 말 그대로 변하지 않는 것을 의미한다.
여기서 말하는 불변성은 외부의 상태나 함수에 인자로 전달된 데이터의 상태를 변경하면 안된다는 것이다.
간단한 예를 살펴보자.
public class FunctionalProgramming {
public static void main(String[] args) {
Item item = new Item(2000);
increasePrice(item);
}
static Item increasePrice(Item item) {
item.price += 1000;
return item;
}
static class Item {
int price;
public Item(int price) {
this.price = price;
}
}
}
- Item이라는 객체의 Price를 변경하는데 있어서, 전달받은 객체의 price를 직접 접근해 변경해주었다.
- 이는 전달받은 데이터를 변경했으므로 불변성을 지키지 못했다.
다음은 불변성을 지킨 예를 살펴보자.
public class FunctionalProgramming {
public static void main(String[] args) {
Item item = new Item(2000);
increasePrice(item);
}
static Item increasePrice(Item item) {
return new Item(item.price+1000);
}
static class Item {
int price;
public Item(int price) {
this.price = price;
}
}
}
- 전달된 데이터를 직접 변경하는 것이 아니라 새로운 객체를 만들어 반환한다.
- 이는 전달된 데이터를 변경하지 않아 불변성을 지켰다.
- 이는 부수 효과를 발생시키지 않는다면 점에서 순수 함수와 연관성이 있다고 볼 수 있으며 이를 통해 동시다발적인 멀티쓰레드 환경에서도 안정적으로 동작할 수 있다.
함수형 프로그래밍은 외부의 상태나 함수에 인자로 전달된 데이터의 상태를 변경하면 안된다.
1급 객체(First Object), 고차 함수(High-Order Function)
1급 객체란, 보통 자바스크립트를 배울 때 많이 나오는 개념이며 함수형 프로그래밍의 중요 전제 조건 중 하나이다.
- 변수나 데이터 구조안에 넣을 수 있다.
- 파라미터로 전달할 수 있다.
- 반환값으로 사용할 수 있다.
- 할당에 사용된 이름과 무관하게 고유한 구별이 가능하다.
그렇다면, 고차 함수란 무엇일까?
고차 함수란, 다음 2가지 조건을 만족하는 함수이다.
- 함수를 파라미터로 받는 함수
- 함수를 리턴하는 함수
함수형 프로그래밍에서는 함수가 1급 객체, 1급 함수로 취급받는다.
그렇다. 고차 함수는 1급 객체의 부분 집합(Subset)이다.
함수형 프로그래밍에서는 함수를 파라미터로 받고 함수를 리턴할 수 있는 형태를 이룸으로써 함수간의 파이프라인이 형성될 수 있도록 한다.
list.stream()
.filter(...)
.map(...)
.collect(...);
함수형 프로그래밍에서는 filter, map, collect와 같은 파이프라인이 형성될 수 있도록 함수를 파라미터로 받고 함수를 리턴하는 1급 함수를 요구하는 것을 확인할 수 있다.
여기서 볼 수 있듯이, for과 switch 같은 expression을 사용하지 않고 filter, map 같은 함수를 매개변수로 받는 메서드를 이용해야한다.
함수형 프로그래밍의 함수는 1급 객체, 고차함수를 만족해야한다.
이처럼, 우리는 불변성을 지키고 순수 함수를 1급 객체로 하여 프로그래밍하는 패러다임을 함수형 프로그래밍이라고 한다.
함수형 프로그래밍은 코드의 가독성을 높이고 유지보수를 용이하게 해주는 등 다양한 장점이 존재하여 자바에서도 JDK 1.8부터 람다식을 도입함으로써 함수형 언어의 길로 들어섰다.
나는 자바의 더 깊은 이해를 위해서는 함수형 프로그래밍에 대한 이해도 필수라는 생각을 하며 이를 살펴보았다.