[functional-programming] ‘클로저’란 무엇입니까?

나는 카레에 관한 질문을하고 폐쇄가 언급되었다. 폐쇄 란 무엇입니까? 카레와는 어떤 관계가 있습니까?



답변

변수 범위

지역 변수를 선언하면 해당 변수에 범위가 있습니다. 일반적으로 지역 변수는 선언 한 블록이나 함수 내에 만 존재합니다.

function() {
  var a = 1;
  console.log(a); // works
}
console.log(a); // fails

로컬 변수에 액세스하려고하면 대부분의 언어가 현재 범위에서 변수를 찾고 루트 범위에 도달 할 때까지 부모 범위를 통해 찾습니다.

var a = 1;
function() {
  console.log(a); // works
}
console.log(a); // works

블록이나 함수가 완료되면 로컬 변수가 더 이상 필요하지 않으며 일반적으로 메모리가 부족합니다.

이것이 우리가 정상적으로 작동하는 방식입니다.

클로저는 영구 로컬 변수 범위입니다.

클로저는 코드 실행이 해당 블록 밖으로 이동 한 후에도 로컬 변수를 유지하는 영구 범위입니다. 클로저를 지원하는 언어 (예 : JavaScript, Swift 및 Ruby)를 사용하면 해당 변수가 선언 된 블록의 실행이 완료된 후에도 참조를 유지하는 경우에도 범위 (상위 범위 포함)에 대한 참조를 유지할 수 있습니다. 그 블록이나 기능에 어딘가에.

스코프 객체와 모든 로컬 변수는 함수에 연결되며 해당 함수가 지속되는 한 지속됩니다.

이것은 우리에게 기능 이식성을 제공합니다. 함수가 처음 정의되었을 때 범위 내에 있던 변수는 나중에 완전히 다른 컨텍스트에서 함수를 호출하더라도 나중에 함수를 호출 할 때 범위 내에있는 것으로 예상 할 수 있습니다.

예를 들어

요점을 보여주는 JavaScript의 간단한 예제는 다음과 같습니다.

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

여기에 함수 내에서 함수를 정의했습니다. 내부 함수는를 포함하여 모든 외부 함수의 로컬 변수에 액세스 할 수 a있습니다. 변수 a는 내부 함수의 범위에 있습니다.

일반적으로 함수가 종료되면 모든 로컬 변수가 사라집니다. 그러나 내부 함수를 반환하고 변수 가 종료 된 fnc후에도 지속되도록 변수에 할당하면 정의 되었을 때 범위 내에 있던 모든 변수 도 유지됩니다 . 변수 가 닫혔습니다 . 변수 가 닫힙니다.outerinnera

변수 a는 전적으로 개인용 fnc입니다. 이는 JavaScript와 같은 기능적 프로그래밍 언어로 개인 변수를 작성하는 방법입니다.

당신이 짐작할 수 있듯이, 호출 fnc()하면 a“1” 의 값을 인쇄합니다 .

클로저가없는 언어에서는 변수 a가 가비지 수집되어 함수 outer가 종료 될 때 버려졌습니다 . a더 이상 존재하지 않으므로 fnc를 호출하면 오류가 발생했습니다 .

JavaScript a에서 변수 범위는 함수가 처음 선언 될 때 작성되고 함수가 계속 존재하는 한 지속되므로 변수 가 지속됩니다.

a의 범위에 속합니다 outer. 의 범위에는의 범위에 inner대한 부모 포인터가 outer있습니다. fnc를 가리키는 변수입니다 inner. a지속되는 한 fnc지속됩니다. a폐쇄 내에 있습니다.


답변

JavaScript를 사용하여 예를 들어 보겠습니다.

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

이 함수 인 makeCounter는 x를 호출 한 함수가 호출 될 때마다 하나씩 카운트되는 함수를 반환합니다. x에 매개 변수를 제공하지 않기 때문에 어떻게 든 카운트를 기억해야합니다. 어휘 범위 지정을 기반으로 찾을 위치를 알고 있습니다. 값을 찾기 위해 정의 된 지점을 찾아야합니다. 이 “숨겨진”값을 클로저라고합니다.

여기 내 커리 예가 있습니다.

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);

add3(4); returns 7

당신이 볼 수있는 것은 매개 변수 a (3)로 add를 호출하면 add3으로 정의 된 반환 된 함수의 클로저에 해당 값이 포함된다는 것입니다. 이런 식으로 add3을 호출하면 덧셈을 수행 할 값을 찾을 위치를 알 수 있습니다.


답변

카일의 대답 은 꽤 좋습니다. 유일한 추가 설명은 클로저가 기본적으로 람다 함수가 생성되는 시점에서 스택의 스냅 샷이라는 것입니다. 그런 다음 기능이 다시 실행될 때 기능을 실행하기 전에 스택이 해당 상태로 복원됩니다. 따라서 Kyle이 언급했듯이 count람다 함수가 실행될 때 숨겨진 값 ( )을 사용할 수 있습니다.


답변

우선, 대부분의 사람들이 말한 것과는 반대로, 폐쇄는 기능 이 아닙니다 ! 그래서 입니다 그것은?
그것은 인 세트 (그 알려진 함수의 “주위 환경”의 정의 심볼 환경 그것 (즉, 각 심볼을 정의하는 값을 갖는다되는 표현식이 때문에이 평가 될 수있다) 밀폐 식 확인).

예를 들어, JavaScript 함수가있는 경우 :

function closed(x) {
  return x + 3;
}

그것은이다 폐쇄 표현 그것에서 발생하는 모든 기호는 그 안에 정의되어 있기 때문에 당신이 그것을 평가할 수 있도록, (그 의미가 명확하다). 즉, 자체 포함되어 있습니다.

그러나 다음과 같은 기능이 있다면 :

function open(x) {
  return x*y + 3;
}

그것은 인 오픈 식 그것에서 정의되지 않은 그것의 심볼들이 존재하기 때문이다. 즉 y. 이 함수를 볼 때, y그 의미와 의미를 알 수 없으며, 그 가치를 모릅니다. 따라서이 표현을 평가할 수 없습니다. 즉, y의미가 무엇인지 알 때 까지이 함수를 호출 할 수 없습니다 . 이것을 자유 변수y 라고 합니다 .

이것은 y정의를 요구하지만이 정의는 함수의 일부가 아닙니다. “주변 컨텍스트”( 환경 이라고도 함)의 다른 곳에 정의되어 있습니다 . 적어도 그것이 우리가 바라는 것입니다 : P

예를 들어, 다음과 같이 전역 적으로 정의 할 수 있습니다.

var y = 7;

function open(x) {
  return x*y + 3;
}

또는 그것을 감싸는 함수로 정의 될 수 있습니다 :

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

표현에서 자유 변수에 의미를 부여하는 환경의 일부는 클로저 입니다. 이 방법 은 모든 자유 변수에 대해 누락 된 정의를 제공 하여 열린 표현식을 닫힌 표현식으로 변환 하므로 평가할 수 있습니다.

위의 예에서 내부 함수 (필요하지 않기 때문에 이름을 지정하지 않은)는 변수 가 자유 로워 개방형 표현식입니다. 정의는 함수 외부에 있습니다. . 해당 익명 함수 의 환경 은 변수 세트입니다.y

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

이제 폐쇄 는 모든 자유 변수에 대한 정의를 제공하여 내부 기능 을 닫는 이 환경의 일부입니다 . 우리의 경우 내부 함수의 유일한 자유 변수는 이므로 해당 함수의 폐쇄는 환경의 하위 집합입니다.y

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

환경에 정의 된 다른 두 기호는 하지 의 부분 폐쇄 가 실행을 필요로하지 않기 때문에, 그 함수의. 그들은 그것을 닫을 필요가 없습니다 .

그 뒤에있는 이론에 대한 자세한 내용은
https://stackoverflow.com/a/36878651/434562

위의 예제에서 래퍼 함수는 내부 함수를 값으로 반환합니다. 이 함수를 호출하는 순간은 함수가 정의 된 (또는 생성 된) 순간부터 원격이 될 수 있습니다. 특히, 랩핑 기능이 더 이상 실행되지 않고 호출 스택에있는 해당 매개 변수가 더 이상 존재하지 않습니다 .P 내부 함수 y가 호출 될 때 내부 함수가 있어야 하기 때문에 문제 가됩니다! 다시 말해, 랩퍼 함수 보다 오래 지속 되고 필요할 때 거기에 있도록 변수의 클로저에서 변수 가 필요합니다. 따라서 내부 함수는 이러한 변수 의 스냅 샷 을 만들어서 닫히고 나중에 사용할 수 있도록 안전한 곳에 보관해야합니다. (콜 스택 외부의 어딘가에 있습니다.)

그렇기 때문에 사람들은 종종 폐쇄 라는 용어 를 그들이 사용하는 외부 변수의 스냅 샷을 만들 수있는 특수한 유형의 함수 또는 나중에 이러한 변수를 저장하는 데 사용되는 데이터 구조로 혼동하는 이유 입니다. 그러나 나는 당신이 그들이 이니까 이해 희망 하지 폐쇄 자체 – 그들은 단지 방법입니다 구현 필요할 때 함수의 폐쇄에서 변수가 될 수있는 프로그래밍 언어 또는 언어 메커니즘에 클로저를. 클로저에 대한 많은 오해가 있습니다.이 주제는 (불필요하게)이 주제를 실제로보다 혼란스럽고 복잡하게 만듭니다.


답변

클로저는 다른 함수의 상태를 참조 할 수있는 함수입니다. 예를 들어, 파이썬에서는 “inner”클로저를 사용합니다 :

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1


답변

클로저에 대한 이해를 돕기 위해 프로 시저 언어로 어떻게 구현되는지 조사하는 것이 유용 할 수 있습니다. 이 설명은 Scheme에서 클로저를 간단하게 구현 한 후 따릅니다.

시작하려면 네임 스페이스 개념을 소개해야합니다. Scheme 인터프리터에 명령을 입력하면 표현식의 다양한 기호를 평가하고 해당 값을 가져와야합니다. 예:

(define x 3)

(define y 4)

(+ x y) returns 7

정의 표현식은 x의 자리에 값 3을, y의 자리에 값 4를 저장합니다. 그런 다음 (+ xy)를 호출하면 인터프리터는 네임 스페이스에서 값을 찾고 작업을 수행하고 7을 반환 할 수 있습니다.

그러나 구성표에는 기호 값을 임시로 재정의 할 수있는 표현식이 있습니다. 예를 들면 다음과 같습니다.

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

let 키워드의 기능은 x가 값 5 인 새로운 네임 스페이스를 도입하는 것입니다. y가 4라는 것을 여전히 볼 수 있으며, 합계는 9로 반환됩니다. 이런 의미에서 x는 일시적으로 로컬 값으로 가려져 있습니다.

절차 언어와 객체 지향 언어는 비슷한 개념을 가지고 있습니다. 전역 변수와 이름이 같은 함수에서 변수를 선언 할 때마다 동일한 효과를 얻습니다.

우리는 이것을 어떻게 구현할 것입니까? 간단한 방법은 연결된 목록을 사용하는 것입니다. 머리에는 새로운 값이 포함되고 꼬리에는 오래된 네임 스페이스가 포함됩니다. 기호를 찾아야 할 때 머리부터 시작하여 꼬리를 따라 내려갑니다.

이제는 일류 함수 구현으로 건너 뛰겠습니다. 대체로 함수는 함수가 반환 값에서 절정이라고 할 때 실행할 명령 집합입니다. 함수를 읽을 때 이러한 명령어를 씬 뒤에 저장하고 함수가 호출 될 때 실행할 수 있습니다.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

x를 3으로 정의하고 더하기 x를 매개 변수 y로 정의하고 x 값을 정의합니다. 마지막으로 우리는 x가 새로운 x에 의해 가려진 환경에서 더하기 -x를 호출합니다.이 값은 5입니다. x가 5 인 경우 반환되는 결과는 9입니다. 이것이 동적 범위 지정입니다.

그러나 Scheme, Common Lisp 및 기타 여러 언어에는 어휘 범위 지정 기능이 있습니다. 연산 저장 (+ xy) 외에도 해당 특정 지점에 네임 스페이스도 저장합니다. 이렇게하면 값을 찾을 때이 문맥에서 x가 실제로 3이라는 것을 알 수 있습니다. 이것은 폐쇄입니다.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

요약하면, 링크 된리스트를 사용하여 함수를 정의 할 때 네임 스페이스의 상태를 저장할 수 있으며, 둘러싸는 범위에서 변수에 액세스 할 수있을뿐만 아니라 나머지 부분에 영향을주지 않고 변수를 로컬로 마스킹 할 수 있습니다. 프로그램.


답변

Closures가 엉덩이를 걷어차는 이유에 대한 실제 예는 다음과 같습니다. 이것은 Javascript 코드와 완전히 다릅니다. 설명하겠습니다.

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

사용 방법은 다음과 같습니다.

var startPlayback = function(track) {
  Player.play(track);
};
startPlayback(someTrack);

이제이 코드 스 니펫이 실행 된 후 5 초 후에 재생이 지연되기를 원한다고 상상해보십시오. 글쎄 그것은 쉽고 delay닫힙니다.

startPlayback.delay(5000, someTrack);
// Keep going, do other things

당신이 호출 할 때 delay5000MS, 최초의 조각 실행 및 저장 그것의 폐쇄에 인수 전달. 그런 다음 5 초 후에 setTimeout콜백이 발생할 때 클로저는 여전히 해당 변수를 유지하므로 원래 매개 변수를 사용하여 원래 함수를 호출 할 수 있습니다.
이것은 일종의 카레 또는 기능 장식입니다.

클로저가 없으면 함수 외부에서 변수의 상태를 어떻게 유지해야하므로 함수 외부에 코드가 논리적으로 속합니다. 클로저를 사용하면 코드의 품질과 가독성이 크게 향상 될 수 있습니다.