- 출처: https://flyingbytes.github.io/programming/java8/functional/part0/2017/01/16/Java8-Part0.html
- 함수형 프로그래밍(Functional programming)
Java 8 을 알아보기 이전에 함수형 프로그래밍을 알아보는게 무슨 소용이냐고 생각할 수 있다. Java 8 에서의 가장 큰 변화중 하나는 stream과 람다(익명함수) 및 함수의 변수화등 함수에 관해 중요한 변화가 있기 때문에 함수형 프로그래밍이란 무엇인가를 먼저 알아보고 살펴보는게 좋을 것 같다는 생각에서 좋은 블로그를 번역하였다.
개발자라면 한번쯤 Functional programming(fp) 에 대해 코드 라인수를 줄여주고 가독성을 향상 시켜준다는 말을 들어본적이 있을 것이다. 하지만 이런 설명은 너무 추상적이어서 함수형으로 프로그래밍을 작성한다는 것 그리고 OOP와는 어떻게 다른지가 명료하지 않을 것이다.
- 모든 변수들은 final 이다.
1
2
3
4
5
6
7
8
|
public String greet(List<String> names) {
String greeting = "Welcome ";
for(String name : names) {
greeting += name + " "; // We don't care about the last space right now.
}
greeting += "!";
return greeting;
}
|
위의 함수는 특정 유저들에게 인사를 하기 위한 문자열을 만드는 함수이다. 하지만 위의 함수는 함수형프로그래밍(이하 "fp") 답다고 할 수 없다. greeting 의 상태를 변화시키는것과 같은 행동은 fp 에서 허용되지 않는다.
그렇다고해서 greeting을 final 로 선언하면 error가 날것이다. += 로 상태를 변화시키고 있기 때문이다.
fp 에서 기본적으로 해야하는일은 1개의 String 으로 변환시킬때 1라인에 모든 이름들을 붙이는것이다.
1
2
3
4
5
|
public String greet(List<String> names) {
final String reducedString = "Welcome " + names.get(0) + " " + names.get(1) + " " + ...
+ names.get(names.size()-1) + " ";
return reducedString + "!";
}
|
당연히 위와 같은 코드는 형편없는 코드이지만 fp 방식으로 프로그래밍을 할 때 인사말을 어떤식으로 만들어야 하는지에 대한 사고방식을 보여준다.
올바른 fp 함수로 표현해보면 아래와 같다. 당장 문법을 이해하지는 못한다고 해도 걱정 할 필요 없이 가볍게 훑어보면된다.
1
2
3
4
5
6
7
8
|
public String greet(List<String> names) {
String greeting = names
.stream()
.map(name -> name + " ")
.reduce("Welcome ",
(acc, name) -> acc + name);
return greeting + "!";
}
|
final 변수에 대한 장점은 값이 언제나 똑같아서 디버깅과 테스팅을 더 쉽게 해준다는점이다.
- 전역변수를 사용하지 말것
전역 time 객체를 예로들어보자. 현재 시간을 문자열로 반환하는 static 함수를 선언했다고 해보자. OO(Object Oriented) 에서는 아래와 같이 작성할것이다. 주로 Project 에서 Util 처럼 사용할 때 아래와 같이 사용하곤 한다.
1
2
3
4
5
6
7
8
9
|
public class Utils {
private static Time time;
public static String currTime() {
return time.getTime().toString();
}
}
|
만약 currTime 을 2번 사용하면 결과는 다른값을 반환하는데, 해당 시간이 다르기 때문이다. currTime 함수를 호출할 때, 입력은 똑같지만(input이 없음), currTime 함수는 2개의 다른 결과를 반환한다.
이것은 fp 에서는 일어나서는 안 되는 일이다. 모든 메소드는 파라미터에만 의존적이어야 한다. 그래서 이런 함수를 작성하고 싶다면 고정된 시간을 입력으로 넘겨야 한다.
1
2
3
4
5
6
7
|
public class Utils {
public static String currTime(FixedTime time) {
return fixedTime.now().toString();
}
}
|
이런 방식은 객체지향의 세계에서는 이상해보이지만 몇 가지 장점이 있다.
우선 가독성이 좋다. 만약 메소드가 파라미터에만 의존적이라면 전역변수나 상태변수를 신경 쓸 필요가 없다. 또 테스트가 훨씬 쉬워진다. fp currTime 메소드를 테스트 할 때, Time 객체를 조정할 수 있다. 반면 OO 형식으로 작성한 코드에서는 static Time 객체를 조정하기가 어렵다.
- 파라미터로서의 Function
fp 에서 함수는 다른 함수의 인자가 될 수 있다. List<Integer>의 모든수에 1을 더하는 함수가 있다고 가정해보자. OO 방식에서는 아래와 같이 코드를 작성할것이다.
1
2
3
4
5
6
7
8
9
|
public List<Integer> addOne(List<Integer> numbers) {
List<Integer> plusOne = new LinkedList<>();
for(Integer number : numbers) {
plusOne.add(number + 1);
}
return plusOne;
}
|
이렇게 되면 2개의 list 를 다루어야 한다. 이런 방식은 혼란을 야기하여 error를 발생시킬 수도 있다. 또 숫자의 상태가 변할 여지가 있다. 이런점들은 후에 프로그램에서 문제가 될 수 있는 부분이다.
fp 에서 List 의 모든 요소에 function 을 사상(map)시킬 수 있다. 아래 예제에서 List 의 모든 요소에 1을 더하여 새로운 List 에 이를 저장한다.
1
2
3
4
5
6
|
public List<Integer> addOne(List<Integer> numbers) {
return numbers
.stream()
.map(number -> number + 1)
.collect(Collectors.toList());
}
|
이렇게 작성하면 변수의 숫자가 줄어들어서 error 를 발생시킬 수 있는 지점이 줄어든다.
fp 는 여러모로 장점이 많다. Final 변수들은 멀티 스레드 사용시에 매우 유용하고, 전역 변수를 줄이는것은 테스트 능력과 함수의 파라미터화는 코드의 품질을 향상시킨다.
댓글