ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 챕터[18] 함수형 관점으로 생각하기
    모던자바인액션 2020. 10. 22. 00:44

    서론

     

    함수형 프로그래밍을 이용하지 않으면 메소드의 호출로 인하여 다른 객체가 변경될 경우 부작용이 발생할 수 있다. 이러한 부작용은 결국 개발자 입장에서 유지보수를 하기 힘들게 만들며, 병렬로 처리하는 과정에서는 언제 어떻게 값이 변경되는지 추적할 수가 없어진다.


     

    공유된 가변 데이터

    어떠한 자료구조도 바꾸지 않으며(자신을 포함하는 클래스의 상태 및 다른 객체) return 문을 통해서 자신의 결과를 반환하는 메서드를 순수 메서드 혹은 부작용 없는 메서드라고 불린다.

    부작용 없는 메서드의 부작용이란?

    • 자료구조를 고치거나 필드에 값을 할당

    • 예외 발생

    • 파일에 쓰기 및 I/O 동작

    선언형 프로그래밍

    어떻게로 접근하는 방식을 선언형프로그래밍이라고 부르기도 한다. 선언형 프로그래밍에서는 우리가 원하는 것이 무엇이고 시스템이 어떻게 그 목표를 달성할 것인지 등의 규칙을 정한다. 문제 자체가 코드로 명확하게 드러나는 점이 선언형 프로그래밍의 장점이다.

     

     

     

    도대체 함수형 프로그래밍은 무엇인가?

    • 함수는 0개 이상의 인수를 가지며, 한 개 이상의 결과를 반환하지만 부작용이 없어야 한다.

    • 자바에서는 순수 함수형이 아니라 함수형 프로그램을 구현할 것이라 실제로는 부작용이 있지만 아무도 보지 못하게 하여 함수형을 당성할 수 있다.

    함수형 조건

    • 지역 변수만 변경한다.

    • 객체를 참조한다면 그 객체는 불변성 객체여야 한다.

    • 함수나 메서드가 어떤 예외도 하면 안된다.

    참조 투명성

    • 부작용을 감추어야 한다는 제약은 참조 투명성 개념으로 귀결된다.

    • 같은 인수로 함수를 호출 시 결과 값은 항상 같아야 한다. 그렇기에 Scanner로 값을 입력받는 다면 참조 투명성을 위배하는 것이다.

    • 동일한 값을 가진 객체를 생성 후 반환 하는 함수를 n번 호출할 경우 값은 동일하지만 서로 다른 객체가 생성되어 다른 메모리에 할당이 된다. 하지만 이러한 문제도 참조 투명성으로 간주된다.

    함수형 실전 연습 예제는 머리가 딸려서 스터디날 물어보겠습니다.

     

    재귀와 반복

    1. 순수 함수형 프로그래밍 언어에서는 while, for 같은 반복문을 사용하지 않는다.

    2. 반복이 이루어 지면서 루프를 탈출하는 조건을 충족하기 위하여 다른 객체를 참조하여 변경이 될 수 있기에 사용하지 않는다.

    3. 그렇기에 반복문 대신 변화가 없는 재귀를 사용한다.

    @Slf4j
    public class ch18FunctionalProgramming {
    
        // 반복 팩토리얼
        public static int factorialIterator(int n) {
            int r = 1;
            for (int i = 1; i <= n; i++) {
                r *= i;
                log.info("r : {}, i : {}", r, i);
            }
            return r;
        }
        // 재귀 팩토리얼
        public static long factorialRecursive(long n) {
            log.info("n : {}", n);
            return n == 1 ? 1 : n * factorialRecursive(n-1);
        }
        // 스트림 팩토리얼
        public static long factorialStreams(long n) {
            log.info("n : {}", n);
            return LongStream.rangeClosed(1, n)
                                .reduce(1, (long a, long b) -> a * b);
        }
        // 꼬리 재귀 팩토리얼
        public static long factorialTailRecursive(long n) {
            log.info("n : {}", n);
            return factorialHelper(1, n);
        }
        // 꼬리 재귀 팩토리얼 헬퍼
        public static long factorialHelper(long acc, long n) {
            log.info("acc : {}, n : {}", acc, n);
            return n == 1 ? acc : factorialHelper(acc * n, n-1);
        }
        
    }

    재귀

    • 재귀 메서드는 일반적 반복 메소드에 비하여 코드가 더 비싸다.
    • factorialRecursive 함수를 호출할 때마다 호출리얼의 입력값에 비례해서 메모리 사용량이 증가한다. 그렇기에 큰 값을 입력할 경우 StackOverFlow가 발생한다.

    꼬리재귀

    재귀에 문제점을 해결한 재귀방법이 꼬리 재귀이다.

    1. 중간 결과를 각각 스택 프레임에 저장해야 하는 일반 재귀와 달리 꼬리 재귀에서는 컴파일러가 하나의 스택 프레임을 재활용할 가능성이 생긴다.

    2. 사실 factorilHelper의 정의에서는 중간 결과를 함수의 인수로 직접 전달한다.

    하지만 자바 8에서는 스트림으로 대체하여 간결하며 부작용 없이 제작할 수 있다.

     

    git : 

    github.com/hodolee246/ModernJavaStudy/blob/master/src/test/java/com/example/modernjava/ch18/ch18FunctionalProgramming.java

    댓글

Designed by Tistory.