클로저(closure)란 무엇인가요?

우연히 Javascript Closure는 Private 변수 만드는 용도인가? 을 읽었습니다.

사실 제목을 보고나서 Private? 응? 이건 OOP 이야기 아닌가?

클로저는 함수형 프로그래밍에서 쓰는 걸로 알고 있는데…

글을 읽다보니 클로저에 대해서 이번에 제대로 알아보고 싶어졌습니다.

글에서 이런 설명을 합니다.

“많은 사람들이 Closure를 Private 변수를 만드는 용도라 한다”

그리고 이는 응용에 하나에 불과하다. (그 마저도 쓰지않는다. 이미 Class가 있으니…)

또 글쓴이께서도 함수형 프로그래밍을 언급하면서 ,

  • 고차함수에는 클로저가 필요하다.

  • 고차함수는 함수를 리턴할 수 있기 때문이다.

  • 리턴되는 함수를 일급함수라 한다.

  • 이 일급함수는 지연실행이 되어야한다.

  • 그래서 스코프(scoop)가 변수 값을 메모리에 저장해 둬야 한다.

MDN함수형 자바스크립트 책 에서도 이런 식으로 언급했던 기억이 어렴풋하게 났습니다.

그래서 우선은 그림이 있으면 그림으로 이해하는게 쉬울 것 같아서 찾아보니 레딧에

이런 그림이 있었습니다만, 여전히 이해하지 못했습니다.

MDN에는 조금 상세히 써 있을까해서 다시 MDN를 읽어보니…
가장 첫 줄에

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.

이런 설명이 있었습니다.
부가적인 설명으로는 이런 설명들이 있었습니다.

변수가 소스코드 내 어디에서 선언되었는지 고려한다.

함수를 리턴하고 리턴하는 함수가 클로저를 형성한다

위에서 언급한 도서에서는

스냅샷을 간직한다.

설명과 함께 이런 예시 코드가 있었습니다.

function makeAddfunction(amount){
    function add(num){
        return num + amount;
    }
    return add;
}

function makeExponetialFunction(base){
    function raise(exponent){
        return Math.pow(base, exponent);
    }
    return raise;
}

var addTento = makeAddfunction(10);
addTento(10); // -> 20

var raiseThreeTo = makeExponetialFunction(3);
raiseThreeTo(3); // -> 9

amount, base 변수는 더 이상 활성 스코프에 없지만,

반환된 함수를 호출하면 여전히 되살릴 수 있다.

중첩된 두 함수 add, raise가 자신의 계산 로직 뿐만 아니라,

자신을 둘러싼 모든 변수의 스냅샷을 간직하고 있기 때문이다.

덧붙여서 이런 설명도 있었습니다.

함수의 클로저는 다음 두 가지를 포함한다.

- 모든 함수 매개변수 (param, param2)

- (전역 변수를 포함해서) 바깥 스코프에 위치한 모든 변수. 

    (additionalVars 함수 이후에 선언된 변수 포함)

 

var outerVar = "Outer";  // 전역변수

function makeInner(params){

    var innerVar = "Inner"  // 함수 내부의 지역 변수


    function inner(){  // inner 함수 선언. 

                           // innerVar, outerVar은 inner 함수 클로저의 일부이다.

        console.log(`${outerVar}, ${innerVar}, ${params}이(가) 보여요!`);

    }

    return inner;

}

var inner = makeInner("Params"); // makeInner를 호출하면 inner 함수가 반환된다.

inner(); // inner 함수는 자신의 외부에 살아있는 평범한 함수다.

// "Outer, Inner, Params이(가) 보여요!"

여기서 덧붙여서 저자는

클로저는 자료구조이다.

간단하게 설명을 했는데, 지금까지 클로저는 자료구조라 명확하게 쓴 글을 이 책에서 밖에 보지 못해서 좀 더 찾아보았습니다.

클로저(closure)는 내부함수가 외부함수의 맥락(context)에 접근할 수 있는 것을 가르킨다. 클로저는 자바스크립트를 이용한 고난이도의 테크닉을 구사하는데 필수적인 개념으로 활용된다. - 생활코딩

클로저(closure)는 자바스크립트에서 중요한 개념 중 하나로 자바스크립트에 관심을 가지고 있다면 한번쯤은 들어보았을 내용이다. execution context에 대한 사전 지식이 있으면 이해하기 어렵지 않은 개념이다. 클로저는 자바스크립트 고유의 개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어(Functional Programming language: 얼랭(Erlnag), 스칼라(Scala), 하스켈(Haskell), 리스프(Lisp)…)에서 사용되는 중요한 특성이다. - 모던 자바스크립트 Deep Dive

클로저는 함수가 속한 렉시컬 스코프를 기억하여 함수가 렉시컬 스코프 밖에서 실행될 때에도 이 스코프에 접근할 수 있게 하는 기능을 뜻한다. - You Don’t Know JS

클로저 = 함수 + 함수를 둘러싼 환경(Lexcial environment)
JS 클로저 = 함수가 생성되는 시점에 생성된다. = 함수가 생성될 때 그 함수의 렉시컬 환경을 포섭하여 실행될 때 이용한다. - 자바스크립트의 스코프와 클로저

클로저는 미스테리 중 하나이다. 인터네에서 자바스크립트에 관한 질문 중 약 23%가 클로저에 대한 글이다.
…중략
꽤 간단하다. 클로저는 원래 있던 곳 근처에 값을 "포착"하는 함수이다. - 마이클 포거스의 함수형 자바스크립트

그 외에도 SICP 에서 클로저에 대한 부분이 있다 들어서 찾아보았는데…
JS에서 말하는 클로저랑은 다른 것 같기도 하고 말이 무슨 말인지 더 어려워서 생략했습니다.

결과적으로 정리해보니,

  • 클로저는 함수형 프로그래밍에서 중요한 개념이다.

  • 클로저를 알기 위해서는 어휘적 범위 지정/스코프을 알아야한다.

  • 클로저는 자료구조, 개념, 기능이다.

이렇게 정리가 되었습니다만…

사실 여전히 잘 이해가 안되며, 모호한 것 같습니다.

또 찾으면 찾을수록 뭐가 뭔지 점점 미궁에 빠지는 듯합니다.

도대체 클로저란 무엇인가요…?

정말 미스테리입니다…


추가로 더 찾아보니…

함수는 중첩될 수 있습니다. 내부 함수 객체가 생성되면 이 객체는 자신을 생성한 외부 함수에 대한 활성 객체의 참조를 가지게 됩니다.
이렇게 함수 객체가 외부 함수에 대한 활성 객체의 참조를 가지는 방식을 클로저라 합니다.
클로저는 프로그래밍 언어의 긴 역사 속에서도 중요한 발견 중 하나입니다.
스킴 언어에서 처음 발견되었죠.
그리고 자바스크립트의 주류에까지 이르렀습니다. 클로저 덕분에 자바스크립트가 더 흥미로운 언어가 되었습니다. 클로저가 없더라면 자바스크립트는 그저 좋은 의도와 실수, 클래스 무더기로 가득 차 있었을 뿐일 것입니다. - 자바스크립트는 왜 그 모양일까?

다른 함수형 언어들을 보아도…
Haskell 문서에서는

Closure는 컴비네이터의 반대어로, 자유변수를 이용하는 함수를 지칭, 클로저는 환경의 어떤 부분을 [닫는다]이다.
f x = (y -> x + y)
f는 클로저를 반환하며, 이는 람다 추상의 외견에 구속되어있는 변수 x가 그 정의 안에 사용되어 있기 때문이다.

Rust 문서에서는

러스트의 클로저는 변수에 저장하거나 다른 함수에 인자로 넘길 수 있는 익명 함수입니다.
한 곳에서 클로저를 만들고 다른 문맥에서 그것을 평가하기 위해 호출할 수 있습니다.
함수와 다르게 클로저는 그들이 호출되는 스코프로부터 변수들을 캡처할 수 있습니다.
이 클로저 특성이 코드 재사용과 동작 사용자 정의를 어떤 식으로 허용하는지 예를 들어 보여줄 것입니다.

엄청난 미스테리입니다.

설명된 내용들은 잘 찾으셨는데 익숙하지 않으셔서 이해를 잘 못하고 계신걸 수도 있겠네요.

예제를 찾아서 참고해보세요.

1 Like

뭔가 이거다!
싶은 느낌이 안 들어서 명쾌한 무엇인가 찾아보아도, 다 비슷한 설명입니다…
좀 더 코드를 찾아서 읽어보겠습니다…

요 부분이 핵심 인데요. 함수형 프로그래밍 에서는 함수가 1급 개체 죠.
무슨 말이냐 이게, 1급 개체 라는게… 원어(?) 로 보면…

In functional programming, functions are treated as first-class citizens, 
meaning that they can be bound to names (including local identifiers), 
passed as arguments, and returned from other functions, 
just as any other data type can.

여기서 핵심은 저 meaning 다음에 오는 부분입니다.

즉 함수가 여타 데이터 타입들 처럼, 다른 함수의 리턴 값도 될수있고, 변수에 저장될 수도 있고, 다른 함수에 인자값으로도 쓰일수 있단 예기죠.

이게 뭐냐… 생각을 좀 더 해 보면, 함수란게 뭔가요. 어떤 임의의 로직 묶음이거든요. 그럼 어따쓰냐, 실행시켜서 어떤 결과를 얻기위해 쓰죠. 인자를 받음으로서, 특정 패턴의 자료를 가공/사용할 수도 있고요.

그런데, 이 함수 라는게 또 다른 성질이, 함수를 만들때는 만드는 쪽 맘대로 이것 저것 할 수 있지만, 사용 하는 입장에선 만들어진걸 사용 만 할 수 있다는 거죠. 만드는 쪽 입장에선, 그쪽 상황에 맞게 만드는거 거든요. 어떤 데이터와 로직을 쓰던 그쪽 자유/상황/필요/꼴리는거란 말입니다. 심지어, 다른 곳에서 만든 함수를 써야 할 필요도 있을것이고요. 그쪽 사정 다 고려해 두고나서, 이제 사용자 입장을 고려한다고 한게, 함수 인자들 몇개 열어 두는 거죠.

따라서, 함수가 만들어 질때와, 실행될때 그 주변 환경이 다르다는 거죠. 그렇지만, 함수는 일급객체로써, 이리저리 주고받을 수도 있고, 언제든 돌아갈 수 있게 되어야 되는 거죠.

여기서 문제가 되는게, 함수가 돌아 가려면, 적어도 일정 조건은 만들어 질때와 사용될때가 같아야 된다는거에요.
그일정 조건이라는건 이제, 함수의 로직에서 사용되는 데이터 들인거죠. 근데 이제, 함수형 프로그래밍에서는, 다른 곳에서 만들어진 함수조차 내 함수로직에 데이터 처럼 사용될 수 있다는 상황인 것이죠.

결국, 디펜던시, 즉 의존성의 문제가 일어 나는 거죠. 뭐에대한?? 내 함수가 돌아갈 수 있는 환경에 대해서 말이죠.

올려 놓으신, 그림에서 내 함수(outer)는 다른 함수(inner)를 결과값으로 리턴 합니다. 근데, 그 inner 라는 함수는 내가 지정한 x 라는 내 함수(outer) 내의 변수에 의존적이죠. 그런데, 사용자는 자기 상황에 맞게 내 함수(outer)를 쓰고 그 결과 값인 inner 함수를 또 이용 해야 된다/할 수있어야 한다 이말이죠.

어… 그럼 어쩌죠? 내 함수(outter)가 실행되고 나서, 결과값 뱉어내고… 나면 내 함수내의 데이터는 out of scope 되고 메모리에서 사라질껀데???

그럼… 그 결과값이 함수(inner)인데 …어… x 변수가… 필요한데??? 그게 돌아 갈려면!!!

ㅈㄷㄴ … gg… 내가 곶…아니…암튼…

짠, 바로 그때, 이 클로져 라는게 짜잔~~~ 등장 합니다. 람다 칼큘러스어딘가에…

A closure is  **a record containing the lambda plus its environment at the time it was created** . 
The closure allows the lambda to be executed at any point in time with its environment providing bindings for all its variables.

내 함수의 리턴값이 될 함수가 돌아 갈 수있는 최소의 환경조건을 스코프에 상관없이 간직하고 있으면 되는 거죠.

초반에 함수형 프로그래밍이 대중의 관심을 받을때, 누군가 물었죠. 아그러니까 클로져가 뭐냐고요 정확히… 누군가가 답을 하는데, 아마도 표현 상의 실수/이유가 있었던지… 일종의 함수라고 표현을 해버린거죠. 그게, 돌고 돌아서 나중에 람다의 의미랑 헷갈려 버리죠. 아놔… 그럼 인제 람다랑 클로져는 뭐가 달라요 해 버리게 되고 ㅋㅋㅋㅋ

자료구조…? 가 함수 보단 좀 가까운거 같아요 느낌적으로…
흠…제가 답을 해 본다면 메카니즘? 정도로 마무리 짓지 않을까 싶네요 ㅎㅎ.

2 Likes

말씀해주신 것처럼 어떤 자료구조, 함수의 기능보다는 메카니즘이라는 표현이 받아들이기 좋은 것 같다는 느낌을 받았습니다. 말이 맞는지는 모르겠는데 클로저는 실체가 없는 개념의 영역 같습니다(아니려나요…)

처음 개념 만든넘들이 이름을 ㅂㅅ같이 지아놔서 그렇죠. 딱 들으면 아~ 하게 해야지 ㅉㅉ

구현상으론 메모리 일정부분에 함수와 그연관 컨텍스트를 킵해 두는거죠. 함수 포인터의 확장정도? 그렇게 보면 자료를 보관하고 쉽게 엑세스할 수있는 data structure 라고 볼 수도 있죠.

더 재미있는건, 스코프를 거스르고 메모리를 점유하는 메커니즘이기 때문에, 과하거나 ㅂㅅ같이 쓰면 메모리 릭도 가능하고, 프로그램의 크래쉬도 낼 수 있겠죠. :slight_smile:

1 Like

함수형 프로그래밍이 기계빨(…)로 한다는게 이런 부분 때문인가보군요…

그나저나 모나드 가기 전에 클로저부터 벌써…

ㅋㅋㅋㅋㅋ 그렇죠.

간단히 말하면 함수를 하나 리턴해주는데, 그 함수가 웬걸 자기만의 전역변수같은걸 들고있네~
입니다 ㅋㅋㅋ

1 Like