함수형 프로그래밍 - 클로저(Closure)

KwonKusang·2021년 7월 28일
3

함수형 프로그래밍의 컨셉

  1. 변경 가능한 상태를 불변상태(immutable)로 만들어 side effect를 없애자.
  2. 모든 것은 객체이다.
  3. 코드를 간결하게 하고 가독성을 높여 구현할 로직에 집중시키자.
  4. 동시성 작업을 보다 쉽게 안전하게 구현 하자.

불변상태로 만들면 어떤 효과를 가져올까?

  • 변수에 접근해야할 때, 읽기 권한만 갖는다. 수정할 수 없기 때문에 데이터가 바뀔 걱정을 하지 않아도 된다.
  • 여러 객체나 스레드에서 접근하더라도 값을 수정할 수 없고, 사용을 위해선 새로운 값을 생성해서 사용한다. thread-safe 하다.
  • 즉, 불변성은 코드를 안전하고 간단하게 만든다.

모든 것은 객체이다.

1급 객체(first class citizen)

  1. 변수나 데이터 구조안에 담을 수 있다.
  2. 파라미터로 전달 할 수 있다.
  3. 리턴 값으로 사용할 수 있다.
  • 자바스크립트에서는 함수를 1급 객체로 취급한다.

순수 함수

함수형 프로그래밍을 적용시켜보며 가장 고민했던 부분이 순수 함수이다.

순수 함수는 어떤 함수에 동일한 인자를 주었을 때 항상 같은 값을 리턴하는 함수이다. 또한 외부의 상태(변수)를 변경하지 않는 함수를 뜻한다.

  • 순수함수의 의도는 부수 효과를 없애고 참조투명성을 갖는 순수 함수를 만들어 모듈화 수준을 높인다.
  • 모듈화 수준이 높으면 재사용성이 높고 좋은 프로그래밍이라 할 수 있다.

의문점...

순수 함수로 구현하는 과정에서 어떤 기준으로 함수를 분할해야 하는지 기준이 세워지지 않았다. 단지, 지금 당장 구현하는 기능에 맞춰서 순수 함수로만 구현하겠다는 욕심에 쓸데없는 기능들로만 이루어진 함수를 구현해야 한다면 객체지향적인 방식이 가독성과 재사용성이 더 좋지 않을까라는 고민을 했다...
기능이 복잡해질 수록 서로 참조가 없는 순수 함수로 구현하기 위해 엄청난 양의 함수가 구현되는 경우도 있지 않을까?

순수 함수에서 다른 순수함수를 호출해서 사용해도 되는가

많은 사람들이 의문점을 갖는 부분이다...내가 직접 함수형 프로그래밍을 구현해보면서 잡았던 기준으로는 X였다.

순수 함수는 참조투명성을 가져야 한다. 때문에 함수 외부의 영향을 받지 않는 것을 의미한다.

만약, 순수 함수 A를 순수 함수 B에서 호출한다고 가정하자.
순수 함수 A의 내부 기능이 새롭게 바뀌고 반환값도 변경되었지만 여전히 순수 함수의 형태를 띄고 있더라도 순수 함수 B의 값은 기존과 다른 결과값을 반환할 것이다.

결국 함수 B는 외부 함수인 A의 영향을 받아 일정한 값을 도출하지 못할 것이라고 생각했다.

순수 함수의 동일한 인자를 주었을 때 항상 같은 값을 리턴한다는 조건을 조금 다르게 해석한 부분이 있지만, 조건의 의도 측면에서 사용하지 않는 쪽이 가깝다고 생각했다.

함수형 프로그래밍을 학습해보며 느낀 개인적인 의견이다.

[2021-07-29]
나는 순수 함수끼리 참조를 할 때 하나의 순수 함수가 변한다면? 변한 이후에도 순수 함수의 형태를 유지하고 있다면? 이라는 가정이 있었다.

하지만 동료들과 토론을 해보니 이 가정 자체가 잘못된 것이 아닐까 라는 의견들이 나왔다. 나의 가정을 온전히 따라가다보면 함수 모듈화를 상당히 많이 해야할테고 함수형 프로그래밍의 의도와 달라지지 않을까 생각하게 되었다. 결국 위에 작성한 의문점... 부분으로 다시 돌아가 고민하게 되지 않을까.

나의 가정의 실수는 수학적으로 f(g(e))라는 것이 함수가 변할 수 있다는 것을 가정하지 않는다고 한다.

새롭게 가정한 기준을 둔다면 순수함수 내에서 순수함수를 사용할 수 있다.라는 의견을 생각하게 되었다.

확실한 정답은 알 수 없지만 의견 공유의 과정 자체가 나의 생각의 폭을 유연하게 늘릴 수 있었다.

Is a nested pure function still a pure function?

클로저(Closure)

선언할 당시의 환경을 기억했다가 나중에 호출될시 원래의 환경에 따라 호출되는 함수
클로저의 사전적인 의미는 포섭이다.

function closure(dataA) {
	return function(dataB) {
		return dataA + dataB
	}
}
const cl = closure(5);
cl(10); // 15 return
  • cl 변수에 데이터 5와 함께 클로저 함수를 선언했다.
  • 위에서 말한 당시의 환경은 5를 매개변수로 넘겨받은 것을 말하며 5포섭했다.
  • 이후 클로저 함수가 담긴 cl 변수를 호출할 때 10 을 넘겨줬다.
  • 선언할 때 넘겼고 포섭 한 데이터 5 와 호출시에 넘긴 10 을 사용하여 결과값을 반환한다.
  • 즉, 함수가 생성될 때 그 함수의 렉시컬 환경포섭하여, 실행될 때 함께 이용된다.
  • dataA와 같은 변수를 자유변수라고 한다.
  • 이것이 클로저의 장점으로 참조할 수 없는 변수로 사용하여 private 효과를 가져온다.

커링(Currying)

여러 개의 인자를 가진 함수를 호출 할 경우, 파라미터의 수보다 적은 수의 파라미터를 인자로 받으면 누락된 파라미터를 인자로 받는 기법을 말한다.

function closure(dataA) {
	return function(dataB) {
		return dataA + dataB
	}
}
closure(5);
/* return 값 
ƒ (dataB) {
   return dataA + dataB
}
*/
closure(5)(10); // return 15
  • 클로저를 먼저 이해한 후 커링을 봤을 때, 변수에 담는 과정을 없앴을 때의 결과라고 생각했다.
  • 당연히 위의 코드에서 변수 cl을 호출하면 같은 결과가 나왔다.
  • 부분적으로 적용된 함수를 체인으로 계속 생성하며 결과값을 도출한다는 말을 충분히 이해할 수 있었다.

클로저 스코프 체인

  • 클로저는 3가지 스코프가 존재한다. MDN 문서에 따르면 다음과 같다.
    • 지역 범위 (Local Scope, Own scope)
    • 외부 함수 범위 (Outer Functions Scope)
    • 전역 범위 (Global Scope)
// 전역 범위 (global scope)
var e = 10;
function sum(a){
  return function sum2(b){
    return function sum3(c){
      // 외부 함수 범위 (outer functions scope)
      return function sum4(d){
        // 지역 범위 (local scope)
        return a + b + c + d + e;
      }
    }
  }
}

var s = sum(1);
var s1 = s(2);
var s2 = s1(3);
var s3 = s2(4);
console.log(s3) //log 20

클로저 - JavaScript | MDN

profile
안녕하세요! 백엔드 개발자 권구상입니다.

0개의 댓글