본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[Java8 In Action] #14 함수형 프로그래밍 기법

by 돼지왕왕돼지 2019. 1. 3.

[Java8 In Action] #14 함수형 프로그래밍 기법


Java8 In Action 내용을 보며 정리한 내용입니다.

정리자는 기존에 Java8 을 한차례 rough 하게 공부한 적이 있고, Kotlin 역시 공부한 적이 있습니다.

위의 prerequisite 가 있는 상태에서 추가적인 내용만 정리한 내용이므로, 제대로 공부를 하고 싶다면 책을 구매해서 보길 권장합니다!


andthen, comparing, compose, computeIfAbsent, first class functio, higher order function, java8 in action, lazy list, memoriztion, pattern matching, persistent 자료구조, referentially transparent, visitor design pattern, 고차원 함수, 방문자 디자인 패턴, 영속 자료구조, 일급 함수, 참조 투명성, 캐시, 커링, 콤비네이터, 패턴 매칭, 함수형 자료구조, 함수형 프로그래밍 기법



14.1. 함수는 모든 곳에 존재한다.


-

일반값처럼 취급할 수 있는 함수를 일급 함수(first-class function)이라고 한다.




14.1.1. 고차원 함수


-

하나 이상의 함수를 인수로 받거나, 함수를 결과를 반환하는 함수를 고차원 함수(higher-order functions) 라 부른다.



-

스트림 연산으로 전달하는 함수는 부작용이 없어야 하며, 부작용을 포함하는 함수를 사용하면 문제가 발생한다. 

부작용을 포함하는 함수를 사용하면 부정확한 결과가 발생하거나 레이스 컨디션 등으로 예상치 못한 결과가 발생할 수 있기 때문이다.

고차원 함수를 적용할 때도 같은 규칙이 적용된다.

따라서 인수로 전달된 함수가 어떤 부작용을 포함하게 될지 정확하게 문서화하는 것이 좋다.

물론 부작용을 포함하지 않을 수 있다면 가장 좋다.




14.1.2. 커링


-

커링은 x 와 y 라는 두 인수를 받는 함수 f 를 한 개의 인수를 받는 g 라는 함수로 대체하는 기법이다.

이 때 g 라는 함수 역시 하나의 인수를 받는 함수를 반환한다.

함수 g 와 원래 함수 f 가 최종적으로 반환하는 값은 같다.


즉 f(x, y) = (g(x))(y) 가 성립한다.


이 커링 과정이 끝까지 완료되지 않은 상태를 가르켜 “함수가 부분적으로 적용되었다” 라고 말한다.



-

static double converter(double x, double f, double b){
    return x * f + b;
}


커링 기법을 적용하면..

static DoubleUnaryOperator curriedConverter(double f, double b){
    return (double x) -> x * f + b;
}





14.2. 영속 자료 구조


-

함수형 프로그램에서는 함수형 자료구조, 불변 자료구조 등의 용어도 사용하지만 보통은 영속(persistent) 자료구조라고 부른다.



-

함수형 메서드에서는 전역 자료구조나 인수로 전달된 구조를 갱신할 수 없다.

자료 구조를 바꾼다면 같은 메서드를 두 번 호출했을 때 결과가 달라지면서 참조 투명성에 위배되고, 인수를 결과로 단순하게 매핑할 수 있는 능력이 상실되기 때문이다.




14.2.1. 파괴적인 갱신과 함수형


-

함수형에서는 계산 결과를 표현할 자료구조가 필요하면 기존의 자료구조를 갱신하지 않도록 새로운 자료구조를 만들어야 한다.

이는 표준 객체지향 프로그래밍의 관점에서도 좋은 기법이다.


함수형을 따르지 않는 프로그램의 문제 중 하나는 부작용을 포함하는 코드와 관련해서 “이 점을 기억해야함”, “문서화해둠” 같은 주석을 프로그래머가 과도하게 남용할 수 있다는 것이다.

이 같은 주석은 나중에 코드를 유지보수하는 프로그래머를 괴롭힌다.




14.2.2. 트리를 사용한 다른 예제




14.2.3. 함수형 접근법 사용


-

함수형 자료구조를 영속(persistent) (저장된 값이 다른 누군가에 의해 영향을 받지 않는 상태) 이라고 하며 따라서 프로그래머는 인수로 전달된 자료구조를 변화시키지 않을 것이라는 사실을 확신할 수 있다.

“결과 자료구조를 바꾸지 말라” 는 것이 자료구조를 사용하는 모든 사용자에게 요구하는 단 한 가지 조건이다.

결과 자료구조를 바꾸지 말라는 조건을 무시한다면 전달된 자료구조에 의도치 않은 그리고 원치 않는 갱신이 일어난다!





14.3. 스트림과 게으른 평가


14.3.1. 자기 정의 스트림




14.3.2. 게으른 리스트 만들기


-

class LazyList<T> implements MyList<T>{
    final T head;
    final Supplier<MyList<T>> tail;

    public LazyList(T head, Supplier<MyList<T>> tail){
        this.head = head;
        this.tail = tail;
    }

    public T head(){
        return head;
    }

    public MyList<T> tail(){
        return tail.get();
    }

    public boolean isEmpty(){
        return false;
    }
}




14.4. 패턴 매칭


-

일반적으로 함수형 프로그래밍을 구분하는 또 하나의 중요한 특징은 구조적인 패턴 매칭 (pattern matching) 이다.

( 정규표현식, 정규표현식과 관련된 패턴 매칭과는 다르다. )



-

수학에서는 다음과 같은 정의를 할 수 있다.


f(0) = 1

f(n) = n*f(n-1)


자바에서는 if-then-else 나 switch 문을 사용해야 한다.

그래서 자료형이 복잡해지면서 이러한 작업을 처리하는 데 필요한 코드의 양도 증가한다.

패턴 매칭을 사용하면 이런 불필요한 잡동사니를 줄일 수 있다.




14.4.1. 방문자 디자인 패턴


-

자바에서는 방문자 디자인 패턴(visitor design pattern)으로 자료형을 언랩할 수 있다.

방문자 클래스는 지정된 데이터 형식의 인스턴스를 입력으로 받는다.

그리고 인스턴스의 모든 멤버에 접근한다.




14.4.2. 패턴 매칭의 힘


-

자바는 패턴 매칭을 지원하지 않는다.

스칼라는 가능한데, 스칼라로 개념만 잡아보자.

def simplifyExpression(expr:Expr): Expr = expr match {
    case BinOp(“+”, e, Number(0)) => e
    case BinOp(“*”, e, Number(1)) => e
    case BinOp(“/“, e, Number(1)) => e
    case _ => expr
}


-

자바로 패턴 매칭 흉내 내기가 가능하다.

그러나 확실히 스칼라에 비해 엄~청 복잡하다..

코드는 책 참조...





14.5. 기타 정보


14.5.1. 캐싱 또는 기억화


-

기억화(memoriztion)라는 기법이 있다.

기억화는 메서드에 래퍼로 캐시(HashMap 같은) 를 추가하는 기법이다.

래퍼가 호출되면 인수, 결과 쌍이 캐시에 존재하는지 먼저 확인한다.

캐시에 값이 존재하면 캐시에 저장된 값을 즉시 반환한다.

캐시에 값이 존재하지 않으면 계산을 수행한 다음 새로운 인수, 결과 쌍을 캐시에 저장하고 결과를 반환한다.

이 기억화 방법은 참조 투명성이 유지되는 상황이라면 간단하게 추가할 수 있다.



-

자바8 에는 computeIfAbsent 라는 메서드가 Map 에 추가되었다.

map 에 내용이 없다면, compute 조건에 넣어준 것을 계산해서 값을 세팅하고 리턴하는 로직을 처리해주는 녀석이다.




14.5.2. ‘같은 객체를 반환함’ 은 무엇을 의미하는가?


-

참조 투명성(referentially transparent)이란 “인수가 같다면 결과도 같아야 한다” 라는 규칙을 만족함을 의미한다.

일반적으로 함수형 프로그래밍에서는 데이터가 변경되지 않으므로 같다는 의미는 == (레퍼런스 같음) 이 아니라 구조적인 값이 같다는 것을 의미한다.




14.5.3. 콤비네이터


-

함수형 프로그래밍에서는 두 함수를 인수로 받아 다른 함수를 반환하는 등 함수를 조합하는 고차원(higher-order) 함수를 많이 사용하게 된다.

이처럼 함수를 조합하는 기능을 콤비네이터라고 부른다.





14.6. 요약


-

일급 함수란 인수로 전달하거나, 결과로 반환하거나, 자료구조에 저장할 수 있는 함수다.



-

고차원 함수란 한 개 이상의 함수를 인수로 받아서 다른 함수를 반환하는 함수다.

자바에서는 comparing, andThen, compose 등의 고차원 함수를 제공한다.



-

커링은 함수를 모듈화하고 코드를 재사용할 수 있도록 지원하는 기법이다.



-

영속 자료구조는 갱신될 때 기존 버전의 자신을 보존한다. 결과적으로 자신을 복사하는 과정이 따로 필요하지 않다.



-

자바의 스트림은 스스로 정의할 수 없다.



-

게으른 리스트는 자바 스트림보다 비싼 버전으로 간주할 수 있다.

게으른 리스트는 데이터를 요청했을 때 Supplier 를 이용해서 요소를 생성한다.

Supplier 는 자료구조의 요소를 생성하는 역할을 수행한다.



-

패턴 매칭은 자료형을 언랩하는 함수형 기능이다.

자바의 switch 문을 일반화할 수 있다.



-

참조 투명성을 유지하는 상황에서는 계산 결과를 캐시할 수 있다.



-

콤비네이터는 둘 이상의 함수나 자료구조를 조합하는 함수형 개념이다.




댓글0