클로저(컴퓨터 프로그래밍)

Closure (computer programming)

프로그램 언어에서 폐쇄(closure) 또는 어휘 폐쇄(function closure)는 1급 함수를 가진 언어에서 어휘 범위 이름 바인딩을 구현하는 기술입니다.운영상 클로저는 환경과 함께 기능[a] 저장하는 레코드입니다.[1]환경은 함수의 각 자유 변수(로컬로 사용되지만 엔클로저 범위에서 정의되는 변수)를 폐쇄를 만들 때 이름이 바인딩된 값 또는 참조와 연결하는 매핑입니다.[b]일반 함수와 달리 닫기를 사용하면 함수가 범위 밖에서 호출된 경우에도 해당 함수의 값이나 참조 복사본을 통해 캡처된 변수에 액세스할 수 있습니다.

역사와 어원

폐쇄의 개념은 1960년대에 λ-캘큘러스에서 표현식의 기계적 평가를 위해 개발되었으며, 어휘 범위 1등급 함수를 지원하기 위해 PAL 프로그래밍 언어의 언어 기능으로 1970년에 처음으로 완전히 구현되었습니다.

Peter Landin은 1964년에 폐쇄라는 용어를 그의 SECD 기계가 표현을 평가하는 데 사용하는 환경 부분제어 부분을 갖는 것으로 정의했습니다.[3]조엘 모제스(Joel Moses)는 Landin이 폐쇄라는 용어를 도입한 것이 어휘 환경에 의해 폐쇄(또는 폐쇄)(또는 결합된) 열린 결합(자유 변수)을 갖는 람다 식을 지칭하는 것이라고 공을 돌립니다.[4][5]1975년 리스프의 어휘 범위 변형인 [6]스킴(Scheme)을 정의하면서 서스먼과 스틸이 이 사용을 채택하여 널리 퍼지게 되었습니다.

Sussman and Abelson은 1980년대 폐쇄라는 용어를 두 번째, 관련 없는 의미로 사용하기도 했습니다. 즉 중첩된 데이터 구조를 추가할 수 있도록 데이터 구조에 데이터를 추가하는 연산자의 속성입니다.이 용어의 사용은 컴퓨터 과학에서의 이전의 사용보다는 수학적 사용에서 비롯되었습니다.저자들은 용어의 이러한 중복을 "불행한 일"이라고 생각합니다.[7]

익명 함수

클로저(closure)라는 용어는 익명 함수의 동의어로 사용되는 경우가 많지만, 엄밀하게는 익명 함수는 이름이 없는 함수 리터럴인 반면 클로저는 함수의 인스턴스(instance)인 값,값 또는 저장 위치(언어에 대한 depending; 아래 어휘 환경 섹션 참조)에 로컬이 아닌 변수가 바인딩되어 있는 경우.

예를 들어 다음과 같은 파이썬 코드에서:

데프 f(x):     데프 g(y):         돌아가다 x + y     돌아가다 g  # 폐쇄를 반환합니다.  데프 h(x):     돌아가다 람다 y: x + y  # 폐쇄를 반환합니다.  # 변수에 특정 폐쇄를 할당합니다. a = f(1) b = h(1)  # 변수에 저장된 폐쇄를 사용합니다. 주장하다 a(5) == 6 주장하다 b(5) == 6  # 변수에 먼저 구속하지 않고 폐쇄를 사용합니다. 주장하다 f(1)(5) == 6  # f(1)은 클로징(closure. 주장하다 h(1)(5) == 6  # h(1)은 클로징(closure. 

의 가치.a그리고.b두 경우 모두 폐색이며, 폐색 함수에서 자유 변수를 갖는 폐색 함수를 반환하여 자유 변수가 모수 값에 결합되도록 합니다.x에워싸는 기능의.폐점은a그리고.b기능적으로 동일합니다.구현의 유일한 차이점은 첫 번째 경우에 이름을 가진 중첩 함수를 사용했다는 것입니다.g, 두 번째 경우에는 익명 중첩 함수(Python 키워드 사용)를 사용했습니다.lambda(익명 함수를 생성하는 경우).이들을 정의할 때 사용되는 원래 이름(있는 경우)은 무관합니다.

종결은 다른 값과 마찬가지로 값입니다.예제의 마지막 두 줄에 표시된 것처럼 변수를 할당할 필요가 없으며 대신 직접 사용할 수 있습니다.이 사용법은 "익명의 종결자"로 간주될 수 있습니다.

내포된 함수 정의는 그 자체가 폐쇄가 아닙니다. 아직 구속되지 않은 자유 변수가 있습니다.포접 함수를 매개 변수 값으로 평가한 후에만 포접 함수가 포접 함수 경계의 자유 변수가 되어 포접 함수에서 반환됩니다.

마지막으로, 폐쇄는 비로컬 변수의 범위를 벗어나는 경우에만 자유 변수가 있는 함수와 구별됩니다. 그렇지 않으면 정의 환경과 실행 환경이 일치하고 이들을 구별할 수 있는 것이 없습니다(이름이 동일한 값으로 결정되므로 정적 바인딩과 동적 바인딩을 구별할 수 없음).예를 들어, 아래 프로그램에서 functions with free variablex(비로컬 변수에 바인딩됨)x글로벌 스코프와 함께)는 다음과 같은 환경에서 실행됩니다.x정의되어 있으므로 실제로 폐쇄인지 여부는 중요하지 않습니다.

x = 1 숫자를 = [1, 2, 3]  데프 f(y):     돌아가다 x + y  지도를(f, 숫자를) 지도를(람다 y: x + y, 숫자를) 

함수가 비국소 변수의 범위 내에서 정의되어야 하기 때문에 함수 반환을 통해 이를 달성하는 경우가 대부분이며, 이 경우 함수 자체의 범위는 일반적으로 더 작습니다.

이는 가변 섀도잉(비로컬 변수의 범위를 줄이는)을 통해 달성할 수도 있지만, 유용성이 떨어지고 섀도잉이 권장되지 않기 때문에 실제로는 덜 일반적입니다.이 예에서는f폐막이라고 볼 수 있는 이유는x의 몸속에f에 구속되어 있습니다.x글로벌 네임스페이스에서.x현지의g:

x = 0  데프 f(y):     돌아가다 x + y  데프 g(z):     x = 1  # local x shadows 글로벌 x     돌아가다 f(z)  g(1)  # 2가 아닌 1로 평가합니다. 

적용들

닫힘의 사용은 함수가 1등급 객체인 언어와 관련이 있으며, 함수는 고차 함수의 결과로 반환되거나 다른 함수 호출에 인수로 전달될 수 있습니다. 사용 가능한 변수가 1등급인 함수의 경우 닫힘을 생성합니다.여기에는 LispML과 같은 기능성 프로그래밍 언어와 줄리아, 파이썬러스트와 같은 많은 현대 다중 패러다임 언어가 포함됩니다.또한 폐쇄는 동적 웹 페이지와의 상호 작용을 위해 사용되는 자바스크립트같은 이벤트 핸들러를 위해 콜백과 함께 자주 사용됩니다.

닫힘은 상태를 숨기기 위해 연속 통과 스타일로 사용할 수도 있습니다.따라서 객체 및 제어 구조물과 같은 구조물을 폐쇄로 구현할 수 있습니다.일부 언어에서는 함수가 다른 함수 내에 정의될 때 닫힘이 발생할 수 있으며 내부 함수는 외부 함수의 지역 변수를 나타냅니다.런타임에 외부 기능이 실행되면 내부 기능의 코드와 폐쇄에 필요한 외부 기능의 변수에 대한 참조(업 값)로 구성된 폐쇄가 형성됩니다.

일급함수

닫힘은 일반적으로 1등급 함수를 가진 언어에서 나타납니다. 즉, 이러한 언어는 문자열 및 정수와 같은 단순한 유형과 마찬가지로 함수를 인수로 전달하거나 함수 호출에서 반환하거나 변수 이름으로 바인딩할 수 있습니다.예를 들어, 다음과 같은 스킴 함수를 생각해 봅니다.

; 판매된 책의 목록을 최소 임계값 이상으로 반환합니다. (정의를 내리다 (베스트셀러 책 문턱을)   (거름망을 치다     (람다 ()       (>= (서적 판매 ) 문턱을))     도서 목록)) 

이 예제에서는 람다 식을 (lambda (book) (>= (book-sales book) threshold))함수 내에 나타납니다.best-selling-books. 람다 식이 평가되면 스킴은 람다 식에 대한 코드와 참조로 구성된 클로징을 생성합니다.thresholdlambda 식 내부의 자유 변수인 variable.

그런 다음 폐쇄는 에 전달됩니다.filterfunction: 결과 목록에 추가할 책과 폐기할 책을 결정하기 위해 반복적으로 호출합니다.왜냐하면 폐쇄 자체가 다음과 관련이 있기 때문입니다.threshold, 매번 그 변수를 사용할 수 있습니다.filter라고 합니다.함수를filter그 자체는 완전히 별개의 파일로 정의될 수도 있습니다.

폐쇄를 지원하는 또 다른 인기 언어인 자바스크립트로 고쳐 쓴 동일한 예는 다음과 같습니다.

// 최소 '임계값'본이 판매된 모든 책 목록을 반환합니다. 기능. 베스트셀러 책(문턱을) {   돌아가다 북리스트.거름망을 치다( => .판매의 >= 문턱을); } 

화살표 연산자=>화살표 함수식을 정의하는 데 사용됩니다.Array.filter전역 대신 방법[8]filter함수, 그러나 그렇지 않으면 코드의 구조와 효과는 동일합니다.

함수는 다음 예제와 같이 클로저를 생성하고 반환할 수 있습니다.

// 도함수 off에 근사하는 함수를 반환합니다. // dx의 간격을 사용합니다. 이 간격은 적절하게 작아야 합니다. 기능. 도함수의(f, dx) {   돌아가다 x => (f(x + dx) - f(x)) / dx; } 

이 경우의 폐쇄는 이를 생성하는 함수의 실행보다 오래되기 때문에 변수는f그리고.dx그 일을 끝까지 해나가다derivative실행 범위를 벗어나 더 이상 보이지 않더라도 반환합니다.닫힘이 없는 언어에서는 자동 로컬 변수의 수명이 해당 변수가 선언된 스택 프레임의 실행과 일치합니다.폐쇄가 있는 언어에서는 기존의 폐쇄가 변수를 참조하는 한 변수가 계속 존재해야 합니다.이것은 가장 일반적으로 어떤 형태의 쓰레기 수집을 사용하여 구현됩니다.

국가대표성

닫기를 사용하여 함수를 몇 번의 함수 호출에 걸쳐 지속되는 "비공개" 변수 집합과 연결할 수 있습니다.변수의 범위는 Closed-over 함수만을 포괄하기 때문에 다른 프로그램 코드에서는 접근할 수 없습니다.이러한 변수는 객체 지향 프로그래밍개인 변수와 유사하며, 실제로 폐쇄는 단일 호출 연산자 방법을 사용하는 상태 저장 함수 객체(또는 함수)와 유사합니다.

따라서 상태 저장 언어에서 폐쇄는 상태 표현 및 정보 숨기기에 대한 패러다임을 구현하는 데 사용될 수 있습니다. 폐쇄의 상위 값(폐쇄 변수)은 무한 범위이므로 한 호출에서 설정된 값은 다음 호출에서 사용할 수 있습니다.이러한 방식으로 사용되는 클로저는 더 이상 참조 투명도를 갖지 않으므로 더 이상 순수한 기능이 아닙니다. 그럼에도 불구하고 일반적으로 스킴과 같은 불순한 기능 언어에서 사용됩니다.

기타용도

클로저는 다양한 용도로 사용됩니다.

  • 폐쇄는 평가를 지연시키기 때문에(즉, 폐쇄가 호출될 때까지), 제어 구조를 정의하는 데 사용할 수 있습니다.예를 들어, 분기(if/then/else) 및 루프(where/for)를 포함한 Smalltalk의 모든 표준 제어 구조는 메서드가 폐쇄를 허용하는 객체를 사용하여 정의됩니다.사용자는 자신의 제어 구조를 쉽게 정의할 수도 있습니다.
  • 할당을 구현하는 언어에서는 동일한 환경에서 가까운 여러 기능을 생성할 수 있으므로 해당 환경을 변경하여 개인적으로 의사소통할 수 있습니다.구성표:
(정의를 내리다 푸우 #f) (정의를 내리다  #f)  (허락하다 ((비밀의 none))   (준비! 푸우 (람다 (msg) (준비! 비밀의 msg)))   (준비!  (람다 () 비밀의)))  (진열하다 ()) ; 인쇄 "none" (새 줄) (푸우 "자정에 부두 옆에서 meet해 주세요.") (진열하다 ()) ; "자정에 부두 옆에서 meet해줘"라고 인쇄합니다. 
  • 객체 시스템을 구현하기 위해 폐쇄를 사용할 수 있습니다.[9]

참고: 일부 화자들은 어휘 환경을 묶는 모든 데이터 구조를 폐쇄라고 부르지만, 이 용어는 일반적으로 함수를 지칭합니다.

구현과 이론

클로저는 일반적으로 함수 코드에 대한 포인터와 클로저가 생성된 시점의 함수의 어휘 환경(즉, 사용 가능한 변수의 집합)을 포함하는 특수 데이터 구조로 구현됩니다.참조 환경은 로컬이 아닌 이름을 클로저가 생성될 때 어휘 환경의 해당 변수에 바인딩하며, 추가적으로 클로저 자체의 수명만큼 수명을 연장합니다.나중에 다른 어휘 환경에서 클로저를 입력하면 현재 환경이 아닌 클로저에서 캡처한 변수를 참조하는 비국소 변수로 함수를 실행합니다.

런타임 메모리 모델이 선형 스택에 모든 자동 변수를 할당하는 경우 언어 구현은 전체 닫힘을 쉽게 지원할 수 없습니다.이러한 언어에서는 함수가 반환될 때 함수의 자동 로컬 변수가 할당 해제됩니다.그러나 폐쇄를 수행하려면 참조하는 자유 변수가 엔클로저 함수의 실행에서 살아남아야 합니다.따라서 이러한 변수는 스택이 아닌 일반적으로 힙 할당을 통해 더 이상 필요하지 않을 때까지 지속되도록 할당해야 하며, 해당 변수를 참조하는 모든 폐쇄가 더 이상 사용되지 않을 때까지 수명이 관리되어야 합니다.

이것은 전형적으로 폐쇄를 기본적으로 지원하는 언어들이 쓰레기 수집을 사용하는 이유를 설명합니다.대안은 로컬 변수가 아닌 변수의 수동 메모리 관리(스택 할당을 명시적으로 수행하고 완료하면 해제)이거나, 스택 할당을 사용하는 경우 C++11[10] 또는 중첩 함수의 람다 식에서와 같이 특정 사용 사례가 해제된 자동 변수에 대한 포인터를 매달아서 정의되지 않은 동작으로 이어질 것임을 언어가 받아들이는 것입니다.gnu C의 ons.[11] funarg 문제(또는 "functional argument" 문제)는 C나 C++와 같은 스택 기반 프로그래밍 언어에서 함수를 퍼스트 클래스 객체로 구현하는 것의 어려움을 설명합니다.D 버전 1에서도 마찬가지로, 프로그래머가 딜러 및 자동 로컬 변수를 어떻게 처리해야 할지 알고 있다고 가정합니다. 딜러의 참조는 정의 범위에서 반환된 후에 무효가 되므로(자동 로컬 변수는 스택에 있음), 이는 여전히 많은 유용한 기능 패턴을 허용합니다.복잡한 경우 변수에 대한 명시적할당이 필요합니다.D 버전 2는 힙에 어떤 변수를 저장해야 하는지 감지하여 이를 해결하고 자동 할당을 수행합니다.D는 가비지 컬렉션을 사용하기 때문에 두 버전 모두에서 변수가 전달될 때 사용을 추적할 필요가 없습니다.

데이터가 불변인 엄격한 함수 언어(예: Erlang)에서는 변수의 참조에 가능한 사이클이 없기 때문에 자동 메모리 관리(가비지 수집)를 구현하는 것이 매우 쉽습니다.예를 들어 Erlang에서는 모든 인수와 변수가 힙에 할당되지만 이에 대한 참조는 스택에 추가로 저장됩니다.함수가 반환된 후에도 참조는 여전히 유효합니다.힙 클리닝은 증분 가비지 수집기에서 수행합니다.

ML에서 로컬 변수는 사전적 범위이므로 스택과 같은 모델을 정의하지만 객체가 아닌 값에 바인딩되므로 프로그래머가 볼 수 없는 방식으로 이러한 값을 클로저의 데이터 구조에 자유롭게 복사할 수 있습니다.

동적 변수와 가비지 컬렉션을 가진 ALGOL과 같은 어휘 스코프 시스템을 가진 Scheme은 스택 프로그래밍 모델이 부족하고 스택 기반 언어의 한계를 겪지 않습니다.폐쇄는 스킴(Scheme)으로 자연스럽게 표현됩니다.람다 형태는 코드를 둘러싸며, 해당 환경의 자유 변수는 접근 가능한 한 프로그램 내에서 유지되므로 다른 스킴 식처럼 자유롭게 사용할 수 있습니다.[citation needed]

폐쇄는 함수의 어휘 환경에 있는 값을 지인이라고 하는 동시 계산의 액터 모델과 밀접한 관련이 있습니다.동시 프로그래밍 언어에서 닫힘의 중요한 문제는 닫힘의 변수를 업데이트할 수 있는지 여부와 업데이트할 수 있는 경우 이러한 업데이트를 동기화하는 방법입니다.행위자들은 하나의 해결책을 제공합니다.[12]

폐쇄는 기능 객체와 밀접한 관련이 있습니다. 전자에서 후자로의 변환을 기능 해제 또는 람다 리프팅이라고 합니다. 폐쇄 변환도 참조하십시오.[citation needed]

의미론의 차이

어휘환경

언어마다 어휘 환경에 대한 공통된 정의가 있는 것은 아니기 때문에 폐쇄에 대한 정의도 다를 수 있습니다.일반적으로 사용되는 어휘 환경에 대한 최소주의적 정의는 범위 내 변수들의 모든 바인딩의 집합으로 정의하며, 또한 어떤 언어에서든 폐쇄가 포착해야 하는 것입니다.그러나 변수 바인딩의 의미도 다릅니다.명령형 언어에서 변수는 값을 저장할 수 있는 메모리의 상대적 위치에 바인딩됩니다.바인딩의 상대 위치는 런타임에 변경되지 않지만 바인딩된 위치의 값은 변경될 수 있습니다.이러한 언어에서 클로저는 바인딩을 캡처하기 때문에 클로저로부터 수행되었든 아니든 변수에 대한 모든 작업은 동일한 상대 메모리 위치에서 수행됩니다.이를 "참조 기준"으로 변수 캡처라고 합니다.다음은 이러한 언어 중 하나인 ECMAscript의 개념을 보여주는 예입니다.

// 자바스크립트 변태의 f, g; 기능. 푸우() {   변태의 x;   f = 기능.() { 돌아가다 ++x; };   g = 기능.() { 돌아가다 --x; };   x = 1;   경계심이 있는('inside 푸, ()를 취소합니다.' + f()); } 푸우();  // 2 경계심이 있는('g ()로 호출:' + g());  // 1 (--x) 경계심이 있는('g ()로 호출:' + g());  // 0 (--x) 경계심이 있는('() 취소:' + f());  // 1(++x) 경계심이 있는('() 취소:' + f());  // 2(++x) 

기능.foo그리고 변수에 의해 언급되는 폐쇄.f그리고.g모두 로컬 변수로 표시된 동일한 상대 메모리 위치를 사용합니다.x.

경우에 따라 위와 같은 동작은 바람직하지 않을 수 있으며, 다른 어휘적 폐쇄를 바인딩할 필요가 있습니다.ECMAscript에서도 이 작업은 다음을 사용하여 수행됩니다.Function.bind().

예제 1: 결합되지 않은 변수 참조

[13]

변태의 모듈 = {   x: 42,   X를 보다: 기능.() {돌아가다 이것..x; } } 변태의 UnboundGetX = 모듈.X를 보다; 위로의.로그.(UnboundGetX()); // 함수는 전역 범위에서 호출됩니다. // 'x'가 전역 범위에 지정되지 않았기 때문에 정의되지 않은 방출입니다.  변태의 바운드GetX = UnboundGetX.제본의(모듈); // 개체 모듈을 폐쇄로 지정합니다. 위로의.로그.(바운드GetX()); // 42개의 방출합니다 

예제 2: 바운드 변수에 대한 우발적 참조

이 예의 경우 예상되는 동작은 각 링크가 클릭 시 ID를 내보내야 하는 것입니다. 그러나 변수 'e'가 위의 범위에 바인딩되어 있고 클릭 시 평가가 게으르기 때문에 실제로 발생하는 것은 각 클릭 시 이벤트가 for 루프의 끝에 바인딩된 'elements'의 마지막 요소의 ID를 내보내는 것입니다.[14]

변태의 요소들 = 문서.getElementsByTagName('a'); // 부정확함: e는 "핸들"을 닫는 것이 아니라 'for' 루프를 포함하는 기능에 바인딩되어 있습니다. 위해서 (변태의 e  요소들) {    e.똑딱똑딱 = 기능. 핸들을() {      경계심이 있는(e.이드);   }  } 

여기서도 변수e를 사용하는 블록의 범위에 의해 구속되어야 할 것입니다.handle.bind(this)아니면let키워드.

반면 ML과 같은 많은 함수 언어는 변수를 값에 직접 결합합니다.이 경우 변수가 바인딩된 후에는 값을 변경할 방법이 없으므로 폐쇄 간에 상태를 공유할 필요가 없으며 같은 값을 사용할 뿐입니다.이를 종종 "값별" 변수 캡처라고 합니다.Java의 로컬 클래스와 익명 클래스도 이 범주에 속합니다. 캡처된 로컬 변수가 필요합니다.final, 상태를 공유할 필요가 없다는 뜻이기도 합니다

일부 언어는 변수의 값을 캡처하거나 변수의 위치를 캡처할 수 있습니다.예를 들어, C++11에서 캡처된 변수는 다음과 같이 선언됩니다.[&], 그 의미는 참조에 의해 포착된 것을 의미합니다.[=], 가치에 의해 포착된다는 뜻입니다.

그러나 하스켈과 같은 또 다른 하위 집합인 게으른 함수 언어는 변수를 값이 아닌 미래 계산 결과에 결합합니다.하스켈의 예는 다음과 같습니다.

-- 해스켈 푸우 :: 분수 a => a -> a -> (a -> a) 푸우 x y = (\z -> z + r)           어디에 r = x / y  f :: 분수 a => a -> a f = 푸우 1 0  주된 = 활자로 찍어내다 (f 123) 

의 구속력.r함수 내에 정의된 폐쇄에 의해 캡처됨foo계산에 해당합니다.(x / y)—이 경우 0으로 나눗셈됩니다.그러나 값이 아닌 캡처된 계산이기 때문에 오류는 클로저가 호출될 때만 나타나고 실제로 캡처된 바인딩을 사용하려고 시도합니다.

마감출차

그러나 더 많은 차이가 다른 어휘 범위 구성의 행동에 나타납니다. 예를 들어 다음과 같은return,break그리고.continue진술들.일반적으로 그러한 구성은 (다음의 경우) 인클로저 제어문에 의해 설정된 탈출 연속을 호출하는 측면에서 고려될 수 있습니다.break그리고.continue, 그러한 해석은 루프 구성을 재귀적 함수 호출(recursive function calls)의 관점에서 고려할 것을 요구합니다.ECMA스크립트와 같은 일부 언어에서는return문장에 관하여 어휘적으로 가장 안쪽에 있는 종결에 의해 성립되는 연속을 말합니다. thus, a.return폐쇄 내에서 통제권을 호출한 코드로 이전합니다.그러나 스몰토크에서 표면적으로 유사한 연산자는^메소드 호출에 대해 설정된 탈출 연속을 호출하고, 모든 중간 중첩 폐쇄의 탈출 연속을 무시합니다.특정 폐쇄의 탈출 계속은 폐쇄 코드의 끝에 도달해야만 Smalltalk에서 암시적으로 호출될 수 있습니다.ECMAscript와 Smalltalk의 예는 다음과 같은 차이점을 강조합니다.

"스몰토크" fuxs xs := #(1 2 3 4)xs do: [:x ^x].^0 bar 녹취: (자체 fo printString) "prints 1"
// ECMA스크립트 기능. 푸우() {   변태의 xs = [1, 2, 3, 4];   xs.에 대하여(기능. (x) { 돌아가다 x; });   돌아가다 0; } 경계심이 있는(푸우()); // 인쇄물 0 

스몰토크 때문에 위의 코드 조각들은 다르게 동작할 것입니다.^오퍼레이터와 자바스크립트return연산자가 유사하지 않습니다.ECMA스크립트 예제에서return x내부 폐쇄를 떠나 새로운 반복을 시작할 것입니다.forEach루프, 반면 스몰토크 예제에서는^x루프를 중단하고 메소드에서 돌아올 것입니다.foo.

Common Lisp은 다음 작업 중 하나를 표현할 수 있는 구문을 제공합니다. Lisp(return-from foo x)스몰토크 행세 ^x, 리스프 중에(return-from nil x)자바스크립트로 동작합니다. return x. 따라서 스몰토크는 캡처된 탈출 계속이 성공적으로 호출될 수 있는 범위보다 오래 지속될 수 있도록 합니다.고려 사항:

"Smalltalk" foo ^[ :x ^x ] bar f f := self foo.f : 123 "error!"

메소드에 의해 클로징이 반환된 경우foo호출되고, 호출에서 값을 반환하려고 시도합니다.foo폐막을 초래한 겁니다해당 호출이 이미 반환되었으며 Smalltalk 메서드 호출 모델이 여러 반환을 용이하게 하기 위해 스파게티 스택 규칙을 따르지 않으므로 이 작업을 수행하면 오류가 발생합니다.

루비와 같은 일부 언어는 프로그래머가 길을 선택할 수 있게 해줍니다.return캡처됩니다.루비의 예:

# 루비  # 절차를 이용한 폐쇄 데프 푸우   f = Proc.신규 { 돌아가다 "인사이드 프로크에서 foo로 돌아옴" }   f.불러 # 여기에 잎을 조절합니다.   돌아가다 "food에서 돌아오다" 끝.  # 람다를 이용한 클로징 데프    f = 람다 { 돌아가다 "람다에서 반환" }   f.불러 # 컨트롤이 여기 막대를 떠나지 않습니다.   돌아가다 "바에서 돌아오다" 끝.  놓다 푸우 # 인쇄 "내부 프로크에서 foo로 돌아가기" 놓다  # "바에서 돌아오다" 인쇄 

둘다요.Proc.new그리고.lambda이 예에서는 클로저를 생성하는 방법이지만, 이렇게 생성된 클로저의 의미는 다음과 관련하여 다릅니다.return진술.

스킴, 정의 및 범위에서returncontrol 문은 명시적입니다(예를 위해 임의로 'return'이라고 이름 붙였을 뿐입니다).다음은 루비 샘플을 직역한 것입니다.

; 스킴 (정의를 내리다 콜/cc 전류와의 통화-계속)  (정의를 내리다 (푸우)   (콜/cc    (람다 (돌아가다)      (정의를 내리다 (f) (돌아가다 "인사이드 프로크에서 foo로 돌아옴"))      (f) ; 여기에 잎을 조절합니다.      (돌아가다 "food에서 돌아오다"))))  (정의를 내리다 ()   (콜/cc    (람다 (돌아가다)      (정의를 내리다 (f) (콜/cc (람다 (돌아가다) (돌아가다 "람다에서 반환"))))      (f) ; 컨트롤이 여기 막대를 떠나지 않습니다.      (돌아가다 "바에서 돌아오다"))))  (진열하다 (푸우)) ; 인쇄 "내부 프로크에서 foo로 돌아가기" (새 줄) (진열하다 ()) ; "바에서 돌아오다" 인쇄 

폐쇄형 구조물

일부 언어에는 폐쇄 동작을 시뮬레이션하는 특징이 있습니다.자바, C++, Objective-C, C#, VB와 같은 언어에서.NET, 그리고 D, 이러한 특징들은 언어의 객체 지향 패러다임의 결과입니다.

콜백( C)

일부 C 라이브러리콜백을 지원합니다.이는 라이브러리에 콜백을 등록할 때 함수 포인터와 별도의 값 두 가지 값을 제공함으로써 구현되기도 합니다.void*사용자가 선택한 임의의 데이터를 가리키는 포인터.라이브러리에서 콜백 기능을 실행하면 데이터 포인터를 따라 전달됩니다.이를 통해 콜백은 상태를 유지하고 라이브러리에 등록된 시점에 캡처된 정보를 참조할 수 있습니다.이 관용구는 기능상 클로저와 비슷하지만 구문상으로는 그렇지 않습니다.void*포인터가 안전한 타입이 아니기 때문에 이 Cidiom은 C#, Haskell 또는 ML의 안전한 타입 폐쇄와 다릅니다.

콜백은 그래픽 위젯(메뉴, 버튼, 체크박스, 슬라이더, 스피너 등)의 일반적인 기능과 애플리케이션에 대한 특정한 원하는 동작을 구현하는 애플리케이션별 기능을 연결하여 이벤트 중심 프로그래밍을 구현하는 GUI 위젯 툴킷에서 광범위하게 사용됩니다.

중첩 함수 및 함수 포인터 (C)

GCC(GNU Compiler Collection) 확장자를 사용하면 중첩 함수를[15] 사용할 수 있고 함수 포인터가 포함된 범위를 벗어나지 않는 경우 닫힘을 에뮬레이션할 수 있습니다.다음 예제는 유효하지 않습니다.adder는 최상위 정의입니다. (컴파일러 버전에 대한 depending, 최적화하지 않고 컴파일하면 정확한 결과를 생성할 수 있습니다. 즉,-O0):

# # <stdio.h>  타이프 디프 인트 (*fn_int_to_int)(인트); // 함수 유형 int->int  fn_int_to_int 덧셈기(인트 번호) {   인트 더하다 (인트 가치) { 돌아가다 가치 + 번호; }   돌아가다 &더하다; // C의 함수 이름이 자신을 가리키는 포인터이므로 & operator는 여기서 선택 사항입니다. }  인트 주된(공허한) {   fn_int_to_int 10을 더하다 = 덧셈기(10);   활자로 찍어내다("%d\n", 10을 더하다(1));   돌아가다 0; } 

하지만 이사는adder(및 선택적으로)typedef)에서main를 유효하게 합니다.

# # <stdio.h>  인트 주된(공허한) {   타이프 디프 인트 (*fn_int_to_int)(인트); // 함수 유형 int->int      fn_int_to_int 덧셈기(인트 번호) {     인트 더하다 (인트 가치) { 돌아가다 가치 + 번호; }     돌아가다 더하다;   }      fn_int_to_int 10을 더하다 = 덧셈기(10);   활자로 찍어내다("%d\n", 10을 더하다(1));   돌아가다 0; } 

실행되면 인쇄됩니다.11역시나

로컬 클래스 및 람다 함수(Java)

Java를 사용하면 메서드 내부에서 클래스를 정의할 수 있습니다.이런 것들을 로컬 클래스라고 합니다.이러한 클래스의 이름이 지정되지 않은 경우 익명 클래스(또는 익명 내부 클래스)라고 합니다.로컬 클래스(이름 있는 클래스 또는 익명 클래스)는 어휘적으로 에워싸는 클래스의 이름 또는 읽기 전용 변수(다음으로 표시됨)를 참조할 수 있습니다.final)를 어휘적으로 둘러싸는 방법으로.

학급 계산 창 확장된 제이프레임 {     사적인 휘발성의 인트 결과;     // ...     일반의 공허한 계산한다.분리된 상태로(최종 URI uri) {         // new Runnable() {...}" 는 'Runnable' 인터페이스를 구현하는 익명 클래스입니다.         신규 (             신규 런너블() {                 공허한 달려.() {                     // 최종 지역 변수를 읽을 수 있습니다.                     계산한다.(uri);                     // 엔클로저 클래스의 개인 필드에 액세스할 수 있습니다.                     결과 = 결과 + 10;                 }             }         ).시작하는();     } } 

포획.finalvariables를 사용하면 값별로 변수를 캡처할 수 있습니다.캡처할 변수가 비-인 경우에도final, 그것은 항상 임시로 복사될 수 있습니다.final수업 직전 변수.

참조에 의한 변수 캡처는 다음을 사용하여 에뮬레이션할 수 있습니다.final가변 컨테이너(예: 단일 element 배열)에 대한 참조.로컬 클래스는 컨테이너 참조 값 자체를 변경할 수 없지만 컨테이너의 내용을 변경할 수 있습니다.

Java 8의 람다 표현식의 등장으로 폐쇄로 인해 위 코드가 다음과 같이 실행됩니다.[16]

학급 계산 창 확장된 제이프레임 {     사적인 휘발성의 인트 결과;     // ...     일반의 공허한 계산한다.분리된 상태로(최종 URI uri) {         // 코드() -> { /* 코드 */ }이(가) 닫힙니다.         신규 (() -> {             계산한다.(uri);             결과 = 결과 + 10;         }).시작하는();     } } 

로컬 클래스는 메서드 본문 내에서 선언되는 내부 클래스 유형 중 하나입니다.또한 Java는 엔클로저 클래스의 비정규 멤버로 선언되는 내부 클래스도 지원합니다.[17]그것들은 보통 "내부 클래스"라고 불립니다.[18]이들은 엔클로저 클래스 본문에 정의되며 엔클로저 클래스의 인스턴스 변수에 완전히 액세스할 수 있습니다.이러한 인스턴스 변수에 대한 바인딩으로 인해 내부 클래스는 특수 구문을 사용하여 엔클로저 클래스의 인스턴스에 대한 명시적 바인딩으로만 인스턴스화될 수 있습니다.[19]

일반의 학급 엔클로저 클래스 {     /* 내부 클래스 정의 */     일반의 학급 이너클래스 {         일반의 인트 증분 및 반환 카운터() {             돌아가다 계산대++;         }     }      사적인 인트 계산대;     {         계산대 = 0;     }      일반의 인트 카운터를 얻다() {         돌아가다 계산대;     }      일반의 정적인 공허한 주된([] 아그스) {         엔클로저 클래스 ClassInstance를 = 신규 엔클로저 클래스();         /* 인스턴스 */에 바인딩된 내부 클래스를 인스턴스화합니다.         엔클로저 클래스.이너클래스 inner 클래스 인스턴스 =             ClassInstance를.신규 이너클래스();          위해서 (인트 i = ClassInstance를.카운터를 얻다();              (i = inner 클래스 인스턴스.증분 및 반환 카운터()) < 10;              /* 증분 단계 생략 */) {             시스템..나가..인쇄된(i);         }     } } 

실행 시 0부터 9까지의 정수가 인쇄됩니다.이러한 클래스 유형을 "정적" 수식어의 동반 사용과 동일한 방식으로 선언되는 중첩 클래스와 혼동하지 않도록 주의하십시오. 이 클래스는 원하는 효과가 없으며 대신 엔클로저 클래스에 정의된 특별한 바인딩이 없는 클래스일 뿐입니다.

자바 8부터 자바는 퍼스트 클래스 객체로서의 기능을 지원합니다.이 형태의 람다 식은 유형으로 간주됩니다.Function<T,U>T는 도메인이고 U는 이미지 유형입니다.그 표현은 다음과 같이 부를 수 있습니다..apply(T t)method, 그러나 표준 method call에서는 그렇지 않습니다.

일반의 정적인 공허한 주된([] 아그스) {     기능.<, 정수> 길이 = s -> s.길이();      시스템..나가..인쇄된( 길이.적용합니다.("안녕, 세상이여!") ); // 13장 인쇄합니다. } 

블록(C, C++, Objective-C 2.0)

애플C, C++, 오브젝티브-C 2.0, 맥 OS X 10.6 "스노우 레오파드"iOS 4.0에 폐쇄 형태인 블록을 비표준 확장으로 도입했습니다.

블록 및 블록 리터럴에 대한 포인터는 다음과 같이 표시됩니다.^. 정상 로컬 변수는 블록이 생성될 때 값으로 캡처되며, 블록 내부에서 읽기 전용입니다.참조로 캡처할 변수는 다음과 같이 표시됩니다.__block. 생성된 범위를 벗어나 지속해야 하는 블록은 복사해야 할 수도 있습니다.[20][21]

타이프 디프 인트 (^Int Block)();  Int Block 다운카운터(인트 시작하는) {   __블록 인트 i = 시작하는;   돌아가다 [[ ^인트() {    돌아가다 i--;   } 알았다.] 자동 해제]; }  Int Block f = 다운카운터(5); NS로그(@"%d", f()); NS로그(@"%d", f()); NS로그(@"%d", f()); 

딜러(C#, VB)NET, D)

C# 익명 메서드 및 람다 식을 통해 폐쇄를 지원합니다.

변태의 데이터. = 신규[] {1, 2, 3, 4}; 변태의 승수 = 2; 변태의 결과 = 데이터..선택한다.(x => x * 승수); 

비주얼 베이직.C#과 유사한 많은 언어 기능을 가진 NET은 클로징이 있는 람다 표현식도 지원합니다.

희미한 데이터. = {1, 2, 3, 4} 희미한 승수 = 2 희미한 결과 = 데이터..선택한다.(기능.(x) x * 승수) 

D에서 클로저는 딜러, 컨텍스트 포인터와 쌍을 이루는 함수 포인터(예: 클래스 인스턴스 또는 클로저의 경우 힙 상의 스택 프레임)에 의해 구현됩니다.

오토 테스트1() {     인트 a = 7;     돌아가다 위임인() { 돌아가다 a + 3; }; // 익명의 대리인 구성 }  오토 테스트2() {     인트 a = 20;     인트 푸우() { 돌아가다 a + 5; } // 내적 기능     돌아가다 &푸우;  // 대리인을 구성하는 다른 방법 }  공허한 () {     오토 dg = 테스트1();     dg();    // =10 // ok, test1.a가 닫힘 상태이지만 여전히 존재합니다.      dg = 테스트2();     dg();    // =25 // ok, test2.a가 닫힘 상태이지만 여전히 존재합니다. } 

D 버전 1은 폐쇄 지원이 제한되어 있습니다.예를 들어, 변수 a가 스택에 있고 test()에서 돌아온 후에는 더 이상 사용할 수 없기 때문에 위의 코드는 올바르게 작동하지 않습니다(대부분 dg()를 통해 foo를 호출하면 'random' 정수가 반환됩니다).이는 힙에 변수 'a'를 명시적으로 할당하거나 구조나 클래스를 사용하여 필요한 닫힌 변수를 모두 저장하고 동일한 코드를 구현하는 방법으로 위임자를 구성하여 해결할 수 있습니다.클로저는 참조된 값이 여전히 유효할 때만 사용되고(예: 클로저를 콜백 파라미터로 사용하는 다른 함수를 호출) 일반 데이터 처리 코드를 작성하는 데 유용하므로 실제로 이러한 제한은 종종 문제가 되지 않습니다.

이 제한은 D 버전 2에서 해결되었습니다. 변수 'a'는 내부 함수에 사용되기 때문에 힙에 자동으로 할당되며, 해당 함수의 위임자는 현재 범위를 벗어날 수 있습니다(dg 할당 또는 반환을 통해).대리인이 참조하지 않거나 현재 범위를 벗어나지 않는 대리인만 참조하는 다른 로컬 변수(또는 인수)는 스택에 남아 있으며, 이는 힙 할당보다 간단하고 빠릅니다.함수의 변수를 참조하는 inner의 클래스 방법도 마찬가지입니다.

함수 개체(C++)

C++는 오버로드를 통해 함수 개체를 정의할 수 있습니다.operator(). 이 객체들은 함수형 프로그래밍 언어의 함수들처럼 다소 동작합니다.런타임에 생성되고 상태를 포함할 수 있지만 폐쇄처럼 로컬 변수를 암시적으로 캡처하지는 않습니다.2011년 개정판에서 C++ 언어는 lambda-expression이라고 하는 특수 언어 구조체로부터 자동으로 구성되는 함수 객체의 한 종류인 클로징도 지원합니다.C++ 클로저는 액세스된 변수의 복사본을 클로저 개체의 구성원으로 저장하거나 참조를 통해 해당 컨텍스트를 캡처할 수 있습니다.후자의 경우, 폐쇄 객체가 참조된 객체의 범위를 벗어나는 경우, 그 객체를 호출합니다.operator()C++ 폐쇄는 컨텍스트의 수명을 연장하지 않으므로 정의되지 않은 동작을 야기합니다.

공허한 푸우(끈을 내 이름) {     인트 y;     벡터를<끈을> n;     // ...     오토 i = std::find_if(n.시작한다.(), n.끝.(),                // 이것이 람다식입니다.                [&](머리를 짜다 끈을& s) { 돌아가다 s != 내 이름 && s.크기() > y; }              );     // 'i'는 이제 'n.end ()'이거나 'n'의 첫 번째 문자열을 가리킵니다.     // 'my name'과 같지 않고 길이가 'y'보다 큰 값 } 

인라인 에이전트(Eiffel)

에펠은 폐쇄를 정의하는 인라인 에이전트를 포함합니다.인라인 에이전트는 루틴을 나타내는 개체로, 루틴의 코드를 인라인으로 부여하여 정의됩니다.예를 들어, 인

ok_버튼.click_event.구독하다 (  대리인 (x, y: INTEGER) 하다   지도를.국가_at_coordin (x, y).진열하다  끝. ) 

에 대한 논쟁.subscribe는 두 개의 인수로 프로시저를 나타내는 에이전트입니다. 프로시저는 해당 좌표에서 국가를 찾아 표시합니다.전체 에이전트가 이벤트 유형에 "가입"됨click_event사용자가 버튼을 클릭했기 때문에 해당 버튼에서 이벤트 유형의 인스턴스가 발생할 때마다 마우스 좌표가 인수로 전달되어 절차가 실행됩니다.x그리고.y.

다른 언어에서의 폐쇄와 구별되는 에펠 에이전트의 주요 한계는 에워싸는 범위에서 지역 변수를 참조할 수 없다는 것입니다.이 설계 결정은 폐쇄에서 지역 변수 값을 말할 때 모호함을 방지하는 데 도움이 됩니다. 변수의 최신 값이어야 합니까, 아니면 에이전트를 생성할 때 캡처한 값이어야 합니까?오직.Current(와 유사한, 현재 객체에 대한 참조this자바)에서, 에이전트 자체의 특징과 인수는 에이전트 본체 내에서 접근할 수 있습니다.외부 로컬 변수의 값은 에이전트에 닫힌 피연산자를 추가로 제공하여 전달할 수 있습니다.

C++Builder __closure 예약어

Embarcadero C++Builder는 함수 포인터와 유사한 구문을 가진 메서드에 포인터를 제공하기 위해 예비 단어 __closure를 제공합니다.[22]

표준 C는 a를 쓰는 것을 허용합니다.다음 구문을 사용하여 함수 유형에 대한 포인터에 대해 typedef를 입력합니다.

타이프 디프 공허한 (*TM 내 함수 포인터)( 공허한 ); 

비슷한 방식으로 다음 구문을 사용하여 메서드에 대한 포인터에 대해 typedef를 선언할 수 있습니다.

타이프 디프 공허한 (__폐쇄 *TMyMethodPointer)(); 

참고 항목

메모들

  1. ^ 함수는 함수 포인터와 같은 함수에 대한 참조로 저장될 수 있습니다.
  2. ^ 이러한 이름은 값, 변동 변수 또는 함수를 나타내는 경우가 많지만 상수, 유형, 클래스 또는 레이블과 같은 다른 엔티티일 수도 있습니다.

참고문헌

  1. ^ 서스먼과 스틸."도식:확장된 람다 미적분학을 위한 인터프리터". "... 람다 식을 포함하는 데이터 구조와 그 람다 식을 인수에 적용할 때 사용할 환경." (Wikisource)
  2. ^ 데이비드 A. 터너 (2012)."함수 프로그래밍 언어의 일부 역사".기능적 프로그래밍의 동향 '12.제2항, 주 8은 M-발현에 관한 청구항을 포함하는 방법.
  3. ^ Landin, P. J. (1964). The mechanical evaluation of expressions (Report).
  4. ^ Moses, Joel (June 1970). The Function of FUNCTION in LISP, or Why the FUNARG Problem Should Be Called the Environment Problem (Report). hdl:1721.1/5854. AI Memo 199. A useful metaphor for the difference between FUNCTION and QUOTE in LISP is to think of QUOTE as a porous or an open covering of the function since free variables escape to the current environment. FUNCTION acts as a closed or nonporous covering (hence the term "closure" used by Landin). Thus we talk of "open" Lambda expressions (functions in LISP are usually Lambda expressions) and "closed" Lambda expressions. [...] My interest in the environment problem began while Landin, who had a deep understanding of the problem, visited MIT during 1966–67. I then realized the correspondence between the FUNARG lists which are the results of the evaluation of "closed" Lambda expressions in LISP and ISWIM's Lambda Closures.
  5. ^ Wikström, Åke (1987). Functional Programming using Standard ML. Prentice Hall. ISBN 0-13-331968-7. The reason it is called a "closure" is that an expression containing free variables is called an "open" expression, and by associating to it the bindings of its free variables, you close it.
  6. ^ Sussman, Gerald Jay; Steele, Guy L. Jr. (December 1975). Scheme: An Interpreter for the Extended Lambda Calculus (Report). AI Memo 349.
  7. ^ Abelson, Harold; Sussman, Gerald Jay; Sussman, Julie (1996). Structure and Interpretation of Computer Programs. Cambridge, MA: MIT Press. pp. 98–99. ISBN 0262510871.
  8. ^ "array.filter". Mozilla Developer Center. 10 January 2010. Retrieved 9 February 2010.
  9. ^ "Re: FP, OO and relations. Does anyone trump the others?". 29 December 1999. Archived from the original on 26 December 2008. Retrieved 23 December 2008.
  10. ^ 람다 표현폐쇄 C++ 표준 위원회2008년2월29일.
  11. ^ GCC Manual, 6.4 Nested Functions "포함된 함수가 종료된 후 주소를 통해 Nested Function을 호출하려고 하면 모든 지옥이 사라집니다.포함된 범위 수준이 종료된 후에 호출하려고 하고 더 이상 범위에 포함되지 않는 변수 중 일부를 언급하는 경우 운이 좋을 수도 있지만 위험을 감수하는 것은 현명하지 않습니다.그러나 중첩 함수가 범위를 벗어난 것을 참조하지 않으면 안전해야 합니다."
  12. ^ 배우 의미론기초 윌 클링어MIT 수학 박사 학위 논문.1981년 6월.
  13. ^ "Function.prototype.bind()". MDN Web Docs. Retrieved 20 November 2018.
  14. ^ "Closures". MDN Web Docs. Retrieved 20 November 2018.
  15. ^ "Nested functions".
  16. ^ "Lambda Expressions (The Java Tutorials)".
  17. ^ "Nested, Inner, Member, and Top-Level Classes".
  18. ^ "Inner Class Example (The Java Tutorials: Learning the Java Language: Classes and Objects)".
  19. ^ "Nested Classes (The Java Tutorials: Learning the Java Language: Classes and Objects)".
  20. ^ "Blocks Programming Topics". Apple Inc. 8 March 2011. Retrieved 8 March 2011.
  21. ^ Bengtsson, Joachim (7 July 2010). "Programming with C Blocks on Apple Devices". Archived from the original on 25 October 2010. Retrieved 18 September 2010.
  22. ^ 전체 설명서는 http://docwiki.embarcadero.com/RADStudio/Rio/en/Closure 에서 확인할 수 있습니다.

외부 링크