함수형 프로그래밍이란..?

roach·2021년 1월 18일
0

Java

목록 보기
2/5

함수형 프로그래밍이란 ?

  • 말 그대로 함수를 이용한 프로그래밍으로, 함수를 인자값으로 사용하거나 리턴값으로 사용할 수 있으며, 순수 함수를 만들어 모듈화 수준을 높이는 프로그래밍

특징

  • 함수가 1등 시민이 된다.
  • 함수를 타입으로 지정할 수 있다.
  • 함수를 인자값으로 넘길 수 있다.
  • 함수를 리턴값으로 받을 수 있다.

1급 객체란?

  • 1급 함수라고도 하며 보통 자바스크립트를 배울때 많이 나오는 내용이나, 자바에서는 함수형 인터페이스를 통해 구현 이 가능합니다.

    특징

    • 변수나 데이터 구조안에 넣을 수 있다.
    • 파라미터로 전달 할 수 있다.
    • 동적으로 프로퍼티 할당이 가능
    • 리턴값으로 사용할 수 있다.

순수함수란 ?

  • 입력값이 동일하면 결과가 동일하게 리턴되는 함수. (Test 할때 정확도)
  • 쉽게 설명하면 add 라는 함수는 10, 5 라는 두 개의 정수가 인자값으로 오면, 무조건 15를 리턴한다.
  • 순수함수가 아닌 함수와 순수함수인 함수의 예시는 아래와 같다.
  • 두번째 함수는 순수함수가 아니다. 왜냐하면 밖에서 C의 값을 바꿔주면, C는 다른 값을 리턴한다.
  • 인자로 입력받는 함수도 순수 함수여야 한다.
  • 함수에서 인자를 변경하거나, 프로그램의 상태를 변경하지 않음
# Ex01
function add(a,b)
	return a+b;

# Ex 02
function add(a,b)
	return a+b+this.c

주요 관심사항

Side-Effect(부작용)

  • Side-Effect 가 일어나게 해서는 안됨. 즉 인자로 단순히 Object 를 넘겨주는 것이 아닌 Function 내 에서 새로운 Object 를 생성하여 전달해 주어야 함.
static Function<Integer, Set<Integer>> factors = (number) -> {
      HashSet<Integer> factors = new HashSet<>(); // 기존 HashSet 을 받는것 보다, 다른걸 사용
        IntStream range = IntStream.rangeClosed(1, (int) Math.sqrt(number));
        range.forEach(ele -> {if(isFactor.apply(number, ele)){factors.add(ele); factors.add(number / ele);}});
      return factors;
    };

// 만약에 받아와야 되는 상황이라면 ?

static BiFunction<Integer, Set<Integer>, Set<Integer>> factors = (number, originalSet) -> {
      HashSet<Integer> factors = Set.copyOf(originalSet); // 기존 HashSet 을 받아서 복사하여 사용
        IntStream range = IntStream.rangeClosed(1, (int) Math.sqrt(number));
        range.forEach(ele -> {if(isFactor.apply(number, ele)){factors.add(ele); factors.add(number / ele);}});
      return factors;
    };

함수를 인자로 전달

static public boolean isDeficient(Function<Integer, Set<Integer>> factors, int number) {
        return sum(factors.apply(number)) - number < number;
		}

멀티쓰레드에서도 안전한가?

  • 그림에서 보듯 Thread 1 의 4 와 , Thread 2 의 4 는 deficient 라는 Type 이라고 정확하게 됨. 객체를 생성하여 사용하므로, 서로 다른 영향을 끼칠 수 없음.

불변성

심볼의 값이 변경되지 않는가? 일단 받아온 객체들은 전부 가변적으로 복사되기 때문에 외부 참조로 인해서 값이 변경될 수 없음. 외부에서 로직도중에 original Set 을 바꾸더라도 의미가 없음. 해당 로직진행은 originalSet 을 복사한 copySet 인 factors 로 진행이 되므로(다른 객체이므로 JVM에서 다른 힙영역을 가지기 때문에)

static BiFunction<Integer, Set<Integer>, Set<Integer>> factors = (number, originalSet) -> {
      HashSet<Integer> factors = Set.copyOf(originalSet); // 기존 HashSet 을 받아서 복사하여 사용
        IntStream range = IntStream.rangeClosed(1, (int) Math.sqrt(number));
        range.forEach(ele -> {if(isFactor.apply(number, ele)){factors.add(ele); factors.add(number / ele);}});
      return factors;
    };

Java 는 True Closure 를 지원하지 않는다 ?

Closure 란?

  • 자바스크립트에서 중요한 개념중 하나로 함수형 프로그래밍을 돕기위해 생긴 개념 이라고 보면되고 사전적 정의는 아래와 같다.
  • 클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다. 간단하게 말하면 외부 범위의 변수를 함수 내부로 바인딩하는 과정이다. 특이한 점은 자신을 둘러싼 외부함수가 종료되어도 이 값이 유지가 된다. (원래는 유지 되지 않음) 그니까 클로저는 lexical enviroment 를 담는다? 라는 표현이 옳은것 같다. 사실 한글로는 잘 표현못하겠따.
  • 클로저는 반환된 내부함수가 자신이 선언됬을때의 환경인 스코프를 기억하여, 자신이 선언됐을 때의 환경밖에서 호출되어도 그 환경에 접근할 수 있는 함수를 말한다. 아래는 자바스크립트의 클로저 예시이다. 자바스크립트의 클로저를 모른다면, 자바스크립트 엔진에 대해서 조금은 공부를 한다면 아래의 코드를 이해할 수 있을 것이다.
function a(){
	let x = 10;
	var innerFunc = fuction() {console.log(x);}
}

function b(x){
		console.log(1 + x);
}

a() // 10

function a(){
	let x = 10;
	var innerFunc = fuction() {console.log(x);}
	return innerFunc;
}

function b(x){
		console.log(1 + x);
}

var inner = b(); 
inner(); // 10

클로저 안되는 이유?

@Pyro(고정완) 에게 남긴 답변

계속 어떻게 구현할 수 있나 찾아보려고 하고 있는데, 제 한계로는 제일 비슷한 구현은 이거네요..

public static void main(String[] args) {
    a();
}

public static void a(){
    Supplier<Integer> callout = add.apply(10);
    Integer integer = callout.get();
    System.out.println("after : " + integer);
}

static Function<Integer, Supplier<Integer>> add = (number) -> {
    int x = 10;
    Supplier<Integer> supplier = () -> {
        System.out.println("Before : " + x);
        return x;
    };
    return supplier;
};

결과값은

Before : 10 after : 10입니다.

그런데 출력값만 비슷하지, 만약 클로저처럼 외부 스코프의 값을 바인딩 해온뒤 참조할 수 있는 일이라면

static Function<Integer, Supplier<Integer>> add = (number) -> {
    int x = 10;
    System.out.println("Inner : " + x);
    Supplier<Integer> supplier = () -> {
        System.out.println("Before : " + x);
        x += 10;
        System.out.println();
        return x;
    };
    return supplier;
};

아래의 코드도 작동해야 할텐데, lamdba 는 최종적인형태로 되어야 한다며 되지않습니다. 아마 자바에서 지원하는 람다는 값을 복사하여 유지하는 것 같습니다. 아마 람다가 들여오는 값조차 final 로 인식하여 들여오기에 새로운값을 할당하지 못하는 것이 아닌가? 이런 추측이 드네요. 그래서 오류가 나는것이 아닐까?클로저는 외부 변수를 참조하고, 람다는 매개변수만 참조하는 목적성인것 같은데 혹시 파이로 생각은 어떤가요??

추신

  • K 가 내가 잘못생각했던것을 짚어줘서 함수형 프로그래밍에 대해서 다시 생각하고, 다른 방식으로 코드를 다시 작성해 볼 수 있었다. 두가지 방식으로 작성해서 차이를 보니, BiFunction 이나 Function 은 Interface 를 값이 아닌 함수를 넘겨 받는 형식의 방식으로 설계할때 쓰면 좋을 듯한 느낌이 들었다.
  • August 가 올려준 유튜브 링크는 재밌고, 쉽게 설명해주는것 같아 좋았다 ㅋㅋㅋㅋ
  • 너무 많이 질문한게 아닌가란 생각이든다? 근데 덕분에 많이배운것 같다.

내코드

내 깃허브 코드! 참조하면 좋을듯 하다

참고하기 좋은 글

https://dinfree.com/lecture/language/112_java_9.html

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

https://medium.com/@la.place/higher-order-function-이란-무엇인가-1c61e0bea79

profile
모든 기술에는 고민을

0개의 댓글