본문 바로가기
Language/Java

[Java 8] 함수형 프로그래밍 개요

by ocwokocw 2021. 2. 10.

- 출처: 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 변수들은 멀티 스레드 사용시에 매우 유용하고, 전역 변수를 줄이는것은 테스트 능력과 함수의 파라미터화는 코드의 품질을 향상시킨다. 

'Language > Java' 카테고리의 다른 글

[Java 8] Stream - 1 (Stream 개요)  (0) 2021.02.11
[Java 8] 람다(Lambda) - 3  (0) 2021.02.11
[Java 8] 람다(Lambda) - 2  (0) 2021.02.10
[Java 8] 람다(Lambda) - 1  (0) 2021.02.10
[Java 8] 객체로서의 함수  (0) 2021.02.10

댓글