본문 바로가기
프로그래밍언어/Java

Java Functional Programming 함수형 프로그래밍으로 가기위한 용어정리 불변객체, 일급함수, 고차함수, 커링

by by 앵과장 2022. 8. 12.
반응형

자바 프로그래밍중 함수형 프로그래밍을 아직 많이 접해보지 않았다면 

아직은 우리에게 익숙한 변수 함수를 이용한 데이터 코드를 보여드리도록 하겠습니다.

 

굳이 약간 어그로가 될수도 있을만한 코드를 공유드립니다.

같은 기능에 대해서 "절차지향 코드",  "객체지향 코드", "함수지향 코드" 3가지를 살펴 보도록 하겠습니다.

 

절차 지향 적인 코드 레벨

변수와 함수를 이용해서 데이터를 가공하는 부분에 초점을 두고있으며

데이터 가공 목적이 강하기 때문에 데이터가 정확하다면 중복된 코드레벨이 발생할수 있지만 단점으로는 길어질수 있는것 같습니다.

그리고 value가 변하게되면 코드들에 Static value들이 존재하기때문에 운영 리소스에 소비되는 시간이 많이 발생해서 피로도가 증가할것 같습니다.

class Main {
    public static String domainData = "test-abc.localhost.com";
    public static void main(String[] args) {
        String domainUrl ="";
        if(domainData.indexOf("test-")==0
          || domainData.indexOf("dev-")==0
          || domainData.indexOf("dev2-")==0
          || domainData.indexOf("local-")==0
          || domainData.indexOf("jts")==0){
            if(domainData.indexOf("localhost")>=0){
                domainUrl = "http://dev-m.xxx.com";
            } else {
                domainUrl = "http://mts.xxx.com";
            }
        } else {
            if(domainData.indexOf("www19")==0
               || domainData.indexOf("www146")==0
               || domainData.indexOf("stg")==0){
                if(domainData.indexOf("www19")==0){
                    domainUrl = "http://m19.xxx.com";
                } else if(domainData.indexOf("test-")==0|| domainData.indexOf("jts.")==0) {
                    domainUrl = "http://stg-m.xxx.com";
                } else {
                    domainUrl = "http://m.xxx.com";
                }
            } else {
                domainUrl = "http://m.xxx.com";
            }
        }
    }
}

 

객체 지향 적인 코드 개발

데이터와 정보를분리 하고

운영 리소스에서도 잘 유지될수 있게 분리함 

final class FilterDomain {
    private final Set<String> domainPrefixFilters;
    private final String mappingDomain;
 
    FilterDomain(String mappingDomain, String ... domainPrefixArgs) {
        this.mappingDomain = mappingDomain;
        this.domainPrefixFilters = Set.of(domainPrefixArgs);
    }
 
    boolean hasDomainPrefix(String domainUrl){
        for(String prefix : domainPrefixFilters){
            if(domainUrl.startsWith(prefix)){
                return true;
            }
        }
            return false;
    }
 
    String resultMappingDomain(String domainUrl, String defaultUrl){
        return hasDomainPrefix(domainUrl) ? mappingDomain : defaultUrl;
    }
}
 
 
public static void main(String[] args){
    String domainData  = "test-abc.localhost.com";
    String defaultUrl = "http://mts.xxx.com";
    FilterDomain  filterDomain = new FilterDomain ("http://dev-m.xxx.com",
        "test-", "dev-", "dev2-", "local-", "jts"
    );
    String domainUrl = filterDomain.resultMappingDomain(domainData, defaultUrl );
}

 

 

함수 지향 적인 코드 개발

일급함수를 사용하여 데이터 정보를 가공

작은 단위로 나눠진 메모리를 빈번하게 사용하는 구조이므로 연산에  따라서 많은 메모리를 사용할수도 있습니다. (모나드, lazy)

public static void main(String[] args){
    final String domainData  = "test-abc.localhost.com";
    String domainUrl = "http://mts.xxx.com"
    boolean match = Set.of("test-", "dev-", "dev2-", "local-", "jts")
                .stream()
                .anyMatch(p->domainData.startsWith(p));
    if(match){
        domainUrl = "http://dev-m.xxx.com"
    }
}

 

 

불변객체 (immutable Object)

목이 잘려나가기 전까지 불멸자였어 ...이영화 갑자기 기억나네

 

불변객체는 한번 생성된 후 그상태가 변경되지 않는 객체를 의미하며 
멀티스레드 환경에서 안전합니다. 

 

불변 객체는 Set , Map 등 자료구조에서 key로 사용되기 매우 좋으며 상태를

알수 없거나 예외 때문에 잘못된 상태를 가질 가능성이 적습니다.

변객체 조건
- 모든 필드는 final로 선언되어야한다.
- 클래스를 final로 선언해서 오버라이드를 방지해야 한다.
- 인수가 없는 생성자는 가급적이면 배제한다.

 

람다

메소드를 한개만 가지고 있는 인터페이스와 같은 취급을 하며 문법적으로 심플한 형태에 Syntax를 정의합니다.

new Function<Integer, String>(){
    @Override
    public String apply(Integer i){
        return String.format("test_%d", i);
    }
};

람다로 표기할때 간단하게 표기하고 호출해서 사용되는 이름을 상세하게 적는편입니다.

(i) -> String.format("test_%d", i);

 

 

일급함수 (first-class function)

FP에서 함수를 마치 일반 값(value)처럼 사용해서 인수로 전달허가나, 결과를 반환 받거나, 자료구조에 저장할수 있습니다.

이렇게 일반값 처럼 취급할 수 있는 함수를 일급 함수 라고 합니다.

 

일급 함수는 내부에 에러를 내포하지 않고, 함수가 실행되는 곳에서 순수하게 값을 계산하는 함수 즉 지정된 값에 에러를 내포하지 않을경우, 정확히 의미한 대로 동작해야하는걸 의미합니다.

 

Java8 이상의 경우, @FunctionalInterface로 지정된 interface의 단일 메소드가 일급 함수로 표현됩니다.

Annotation으로 지정할경우 해당 인터페이스를 람다 형태로 표현이 가능합니다.

일급함수가 무엇인지 아시겠습니까 ?!

고차원 함수(Higher-order function)

하나 이상의 일급 함수를 인수로 받거나, 일급 함수를 결과로 반환하는 함수를 고차원 함수라고 합니다.

대표적으로 map, filter, forEach등의 고차함수가 있습니다.

 

// 01. map고차 함수를 이용해 데이터 가공 예제
Arrays.asList("a", "b", "c").stream()
    .map(String::toUpperCase)
    .forEach(System.out::println);
 
// 02. Function에 일급 함수를 받는 형태로도 구성 가능
Function<Function<Integer, Integer>, Function<Integer, Integer>> twice = f -> f.andThen(f);
twice.apply(x -> x + 3).apply(7); // 13
부작용과 고차원 함수
부작용을 포함하는 함수를 사용하면 부정확한 결과가 발생하거나 레이스 컨디션(race conditions : 경쟁상태)때문에 예상치 못한 결과가 발생할수 있습니다.
고차원 함수를 적용할때도 같은 규칙이 적용됩니다.
고차원 함수나 메소드를 구현할 때 어떤 인수가 전달 될지 알 수 없으므로 인수가 부작용을 포함할 가능성을 염두에 두어야 합니다.
함수를 인수로 받아 사용하면서 코드가 정확히 어떤 작업을 수행하고 프로그램의 상태를 어떻게 바꿀지 예측이 어려워 지고 디버깅도 어려워지기 때문에 인수로 전달된 함수가 어떤 부작용을 포함하게 될지 정확하게 주석이나 설명을 정리하는것이 필요합니다.
커링 (curring)

일급함수를 생성하는 함수 팩토리를 가르켜 커링이라고합니다.

커링을 자주 하게되면, 함수의 파라메타 개수가 주는 효과를 가져오게됩니다.

// 1. 커링 정의
static DoubleUnaryOperator curriedConverter(double f, double b){
  return (double x) -> x * f + b;
}
 
// 2. 커링을 활용하여 일급 함수 생성
DoubleUnaryOperator convertCtoF = curriedConverter(9.0/5, 32); // 섭씨를 화씨로 변환
DoubleUnaryOperator convertKmtoMi = curriedConverter(0.6214, 0); // 킬로미터를 미터로 변환
 
 
// 3. 일급함수 사용
convertCtoF.applyAsDouble(25);
커링은 x와 y라는 두 인수를 받은 함수 f를 편의를 위해 한 개의 인수를 받는 g라는 함수로 대체하며 단순화 하는 기법입니다.
이때 g라는 함수 역시 하나의 인수를 받는 함수(커링)를 반환 합니다.
함수 g와 원래 함수 f가 최종적으로 반환 하는값은 동일합니다.

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

커링(currying) vs 부분 적용(partial application)

커링 : 다인수 함수를 일 인수 함수들의 체인으로 바꾸는 방법

부분 적용 : 다인수 함수에 부분적으로 생략 가능한 인수의 값이 미리 정해 더작은 수의 인수를 받는 하나의 함수로 변환하는 방법

개발관점에서 커링은 함수팩토리, 부분적용은 메소드 오버로딩 관점으로 해석될수 있습니다.