맨위로가기

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

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

클로저는 함수가 정의된 렉시컬 환경을 기억하여 비지역 변수를 값 또는 저장 위치에 바인딩하는 함수 인스턴스이다. 1960년대 람다 대수 평가를 위해 개발되었으며, 1970년대 PAL 프로그래밍 언어에서 처음 구현되었다. 클로저는 함수형 프로그래밍 언어에서 주로 사용되며, 콜백 함수, 정보 은닉, 객체나 제어 구조 구현 등에 활용된다. 클로저는 함수 포인터와 자료 구조를 사용하여 구현되며, 스택 기반 메모리 할당 언어에서는 완전한 지원이 어렵다. 클로저를 지원하는 언어는 Scheme, Lisp, Python, JavaScript, C++, Java 등이 있다.

더 읽어볼만한 페이지

  • 프로그래밍 언어 개념 - 참조
    참조는 프로그래밍에서 메모리 주소나 다른 데이터를 가리키는 값으로, 데이터의 효율적인 전달과 공유를 위해 사용되며, 포인터, 파일 핸들, URL 등이 그 예시이다.
  • 프로그래밍 언어 개념 - 자료형
    자료형은 프로그래밍 언어에서 데이터를 분류하고 관리하는 추상적인 분류 체계로, 값의 표현, 해석 및 구조에 제약 조건을 가하여 프로그램의 정확성을 검증하며, 단순형/복합형, 언어 정의형/사용자 정의형 등으로 분류되고 문자형, 수치형, 부울형 등 다양한 종류가 있다.
  • 함수 (프로그래밍) - 사용자 정의 함수
    사용자 정의 함수는 프로그래밍 언어와 데이터베이스 시스템에서 사용자가 직접 정의하여 재사용할 수 있는 코드 블록이다.
  • 함수 (프로그래밍) - 코루틴
    코루틴은 실행을 멈췄다가 다시 시작할 수 있는 서브루틴의 특별한 형태로, 로컬 데이터를 보존하며 다양한 방식으로 구현되고 여러 프로그래밍 상황에서 유용하게 쓰인다.
클로저 (컴퓨터 프로그래밍)

2. 역사와 어원

클로저의 개념은 1960년대 람다 대수의 표현식을 기계적으로 평가하기 위해 개발되었으며, 1970년에 PAL 프로그래밍 언어에서 일급 함수를 지원하기 위해 언어 기능으로 처음 완전히 구현되었다.[2]

피터 랜딘은 1964년에 자신의 SECD 머신이 표현식을 평가할 때 사용되는 '환경 부분'과 '제어 부분'을 가진 용어 '클로저'를 정의했다.[3] 조엘 모세스는 랜딘이 어휘 환경에 의해 닫힌(또는 바인딩된) 열린 바인딩(자유 변수)을 가진 람다 표현식을 지칭하기 위해 '클로저'라는 용어를 도입하여 '닫힌 표현식', 즉 클로저를 만들었다고 설명한다.[4][5] 이러한 사용법은 1975년 Scheme을 정의할 때 서스만과 스틸에 의해 채택되었고,[6] 이는 Lisp의 어휘적으로 유효한 변형이었으며 널리 퍼지게 되었다.

서스만과 에블슨은 1980년대에 '클로저'라는 용어를 다른 의미로 사용했는데, 이는 자료 구조에 데이터를 추가하는 연산자가 중첩된 자료 구조도 추가할 수 있는 속성을 의미한다. 이 용어의 사용은 컴퓨터 과학에서의 이전 사용보다는 수학적 사용에서 비롯되었다. 저자들은 이 용어의 중복 사용이 "불행하다"고 생각한다.[7]

3. 클로저의 개념

클로저는 익명 함수와 혼동되기도 하지만, 엄밀히 말하면 익명 함수는 이름이 없는 함수 리터럴이고, 클로저는 비지역 변수가 값 또는 저장 위치에 바인딩된 함수의 인스턴스이다.[8] 클로저는 함수가 정의될 때의 환경(변수, 함수 등)을 기억하고, 함수가 호출될 때 이 환경을 함께 사용한다.

클로저는 비지역 변수의 범위를 벗어났을 때 자유 변수가 있는 함수와 구별된다. 정의 환경과 실행 환경이 일치하면, 이름이 동일한 값으로 해석되므로 정적 바인딩과 동적 바인딩을 구별할 수 없다.

클로저는 일반적으로 일급 함수를 가진 언어에서 나타난다. 즉, 함수를 문자열이나 정수와 같은 간단한 유형처럼 인수로 전달하고, 함수 호출에서 반환하고, 변수 이름에 바인딩하는 등의 작업을 가능하게 한다.

예를 들어, Scheme 코드에서 람다 표현식이 평가될 때, 람다 표현식의 코드와 람다 표현식 내부의 자유 변수인 `threshold` 변수에 대한 참조로 구성된 클로저를 생성한다.

```scheme

; THRESHOLD 이상의 판매량을 가진 모든 책의 목록을 반환합니다.

(define (best-selling-books threshold)

(filter

(lambda (book)

(>= (book-sales book) threshold))

book-list))

```

자바스크립트에서는 전역 `filter` 함수 대신 `Array.filter` 메서드[8]를 사용하지만, 코드의 구조와 효과는 동일하다.

```javascript

// 'threshold'개 이상의 판매량을 가진 모든 책의 목록을 반환합니다.

function bestSellingBooks(threshold) {

return bookList.filter(book => book.sales >= threshold);

}

```

함수는 클로저를 생성하여 반환할 수 있는데, 이 경우 변수 `f`와 `dx`는 `derivative` 함수가 반환된 후에도 유지되며, 비록 실행이 해당 범위를 벗어나더라도 마찬가지이다.

```javascript

// dx의 간격을 사용하여 f의 도함수를 근사하는 함수를 반환합니다. dx는 적절하게 작아야 합니다.

function derivative(f, dx) {

return x => (f(x + dx) - f(x)) / dx;

}

```

클로저가 없는 언어에서 자동 지역 변수의 수명은 해당 변수가 선언된 스택 프레임의 실행과 일치한다. 그러나 클로저가 있는 언어에서 변수는 존재하는 모든 클로저가 해당 변수에 대한 참조를 가지고 있는 한 계속 존재해야 하며, 이는 가비지 컬렉션을 통해 구현된다.

3. 1. 예시

python

def f(x):

def g(y):

return x + y

return g # 클로저 반환

def h(x):

return lambda y: x + y # 클로저 반환

# 특정 클로저를 변수에 할당

a = f(1)

b = h(1)

# 변수에 저장된 클로저 사용

assert a(5) == 6

assert b(5) == 6

# 먼저 변수에 바인딩하지 않고 클로저 사용

assert f(1)(5) == 6 # f(1)은 클로저

assert h(1)(5) == 6 # h(1)은 클로저

```

`a`와 `b`의 값은 클로저이며, 두 경우 모두 자유 변수를 포함하는 중첩 함수를 바깥쪽 함수에서 반환하여 생성되므로, 자유 변수는 바깥쪽 함수의 매개변수 `x`의 값에 바인딩된다. `a`와 `b`의 클로저는 기능적으로 동일하다. 구현상 유일한 차이점은 첫 번째 경우 이름이 있는 중첩 함수 `g`를 사용했고, 두 번째 경우 익명 함수를 만들기 위해 파이썬 키워드 `lambda`를 사용했다는 것이다. 정의에 사용된 원래 이름(있는 경우)은 관련이 없다.

클로저는 다른 값과 마찬가지로 값이다. 변수에 할당할 필요가 없으며 예시의 마지막 두 줄에 표시된 것처럼 직접 사용할 수 있다.

중첩 함수 정의 자체는 클로저가 아니다. 아직 바인딩되지 않은 자유 변수가 있다. 바깥쪽 함수가 매개변수 값을 사용하여 평가된 후에만 중첩 함수의 자유 변수가 바인딩되어 클로저가 생성되고, 이는 바깥쪽 함수에서 반환된다.

4. 클로저의 용도

클로저는 다양한 용도로 활용된다.


  • 라이브러리 설계자는 함수(콜백 함수)를 인수로 받는 함수(고차 함수)를 정의하여 사용자가 동작을 사용자 정의할 수 있는 범용적인 라이브러리 함수를 제공할 수 있다. 이때, 클로저를 고차 함수의 인수로 전달하면, 기술을 단순화하고 고차 함수 외부의 상태를 참조할 수 있게 된다. 예를 들어, 컬렉션의 정렬을 수행하는 함수는 비교 함수를 인수로 전달하여 사용자가 정의한 기준에 따라 정렬할 수 있게 되는데, 클로저를 사용하면 더욱 자유로운 비교 처리를 간결하게 기술할 수 있게 된다.
  • 클로저는 지연 평가되므로(호출될 때까지 아무것도 실행하지 않음) 제어 구조의 정의에 사용할 수 있다. 예를 들어, Smalltalk영어의 분기(if-then-else)나 반복(while, for)을 포함한 모든 표준 제어 구조는 클로저를 인수로 받는 메서드를 가진 객체를 사용하여 정의된다. 사용자는 유사한 방법으로 직접 제작한 제어 구조를 쉽게 정의할 수 있다.
  • 지연 평가되는 인자처럼, 그 값을 구하기 위한 것은 갖춰져 있지만 아직 값 자체는 계산되지 않은 것을 기억해두기 위해, 추가 인자를 가지지 않는 클로저와 같은 데이터 구조를 사용한다. 이것을 썽크(thunk)라고 한다. ALGOL 60의 이름 호출 구현에서 고안되었다.


클로저는 함수를 여러 번 호출하는 동안 지속되는 일련의 "비공개" 변수 집합과 함수를 연결하는 데 사용될 수 있다. 변수의 스코프는 닫힌 함수만 포함하므로 다른 프로그램 코드에서는 접근할 수 없다. 이는 객체 지향 프로그래밍의 비공개 변수와 유사하며, 실제로 클로저는 단일 호출 연산자 메서드를 가진 상태가 있는 함수 객체 (또는 펑터)와 유사하다.[1]

상태가 있는 언어에서 클로저는 클로저의 업밸류(닫힌 변수)가 무한 범위를 가지므로, 한 번의 호출에서 설정된 값이 다음 호출에서도 사용 가능하므로 상태 표현 및 정보 은닉 패러다임을 구현하는 데 사용할 수 있다. 이러한 방식으로 사용되는 클로저는 더 이상 참조 투명성을 갖지 않으며, 따라서 더 이상 순수 함수가 아니지만, Scheme과 같은 불순수 함수형 언어에서 일반적으로 사용된다.[2]

5. 클로저를 지원하는 프로그래밍 언어

일급 함수를 지원하는 언어에서 주로 클로저가 사용된다.

6. 의미론적 차이

클로저의 정확한 동작 방식은 프로그래밍 언어마다 다를 수 있다.
변수 바인딩명령형 언어에서 변수는 값을 저장하는 메모리 위치에 바인딩된다. 클로저는 이 바인딩을 포착하므로, 변수 조작은 클로저 내부, 외부 어디에서든 동일한 메모리 영역에서 실행된다.

반면, ML과 같은 함수형 언어는 변수를 값에 직접 바인딩한다. 따라서 변수 값을 변경할 수 없으므로 클로저 간 상태 공유가 필요 없다.

Haskell과 같이 지연 평가를 하는 함수형 언어는 변수를 미래 계산 결과에 바인딩한다. 오류는 클로저가 실행되어 바인딩을 사용하려 할 때 발생한다.
제어 구조정적 스코프인 C 언어 스타일 언어의 `return`, `break`, `continue` 등에서 차이가 나타난다. ECMAScript 등에서 이들은 클로저마다 바인딩되어 구문상 바인딩을 숨긴다. 즉, 클로저 내 `return`은 클로저 호출 코드에 제어를 넘긴다. 그러나 스몰토크에서 이러한 동작은 최상위 레벨에서만 일어나며, 클로저에 포착된다.

```smalltalk

"Smalltalk"

foo

| xs |

xs := #(1 2 3 4).

xs do: [:x | ^x].

^0

bar

Transcript show: (self foo) "prints 1"

```

```javascript

// ECMAScript

function foo() {

var xs = new Array(1, 2, 3, 4);

xs.forEach(function(x) { return x; });

return 0;

}

print(foo()); // prints 0

```

스몰토크의 `^`는 ECMAScript의 `return`과 비슷해 보이지만, ECMAScript에서 `return`은 클로저를 빠져나가지만 함수 `foo`는 빠져나가지 않는다. 반면 스몰토크에서 `^`는 클로저와 메서드 `foo` 모두 빠져나간다.

하지만, 스코프를 넘어 생존하는 지속에는 문제가 있다.

```smalltalk

foo

^[ x: | ^x ]

bar

| f |

f := self foo.

f value: 123 "error!"

```

위 예에서 메서드 `foo`가 반환하는 블록 실행 시, `foo`에서 값을 반환하려 하지만 `foo` 호출은 이미 완료되었으므로 오류가 발생한다.

7. 클로저와 유사한 언어 기능

C++C#, D, Java, Objective-C, VB.NET과 같은 언어들은 객체 지향 패러다임의 결과로 클로저와 유사한 기능을 제공한다.[15]

7. 1. C

C 언어 라이브러리 중에는 콜백을 지원하는 경우가 있다. 이때 함수 포인터와 사용자 지정 데이터 (주로 `void*` 포인터)를 함께 전달하여 상태를 유지하고 정보를 참조할 수 있게 한다.[15] 이러한 방식은 클로저와 기능적으로 유사하지만, 구문적으로는 다르다. `void*` 포인터는 타입 안전성이 보장되지 않기 때문에 C#, 하스켈, ML 등의 타입 안전 클로저와는 차이가 있다.

콜백은 그래픽 사용자 인터페이스(GUI) 위젯 툴킷에서 널리 사용된다. 이를 통해 메뉴, 버튼, 체크 상자 등 그래픽 위젯의 일반적인 기능을 특정 동작과 연결하여 이벤트 기반 프로그래밍을 구현한다.

GNU 컴파일러 모음(GCC) 확장을 사용하면 중첩 함수를 통해 클로저와 비슷한 기능을 구현할 수 있다.[15] 하지만 중첩 함수가 포함된 범위를 벗어나면 제대로 동작하지 않을 수 있다.

C 언어에서 콜백을 지원하는 라이브러리 함수는 함수 포인터와 추가적인 임의의 데이터 포인터(예: `void*`)를 받는다.

```c

typedef int CallbackFunctionType(void* userData);

extern int callUserFunction(CallbackFunctionType* callbackFunction, void* userData);

```

`callUserFunction` 함수는 `callbackFunction`을 실행할 때 데이터 포인터 `userData`를 실행 문맥으로 사용한다. 이를 통해 콜백은 상태를 관리하고 등록된 임의의 정보를 참조할 수 있다. 이는 클로저와 기능은 유사하지만 구문은 다르다.

7. 2. C++

C++(C++)는 `operator()`를 오버로딩하여 함수 객체를 정의할 수 있다. 이러한 객체는 함수형 프로그래밍 언어의 함수와 다소 유사하게 동작한다. 런타임에 생성될 수 있으며 상태를 포함할 수 있지만, 클로저처럼 지역 변수를 암묵적으로 캡처하지는 않는다. 2011년 개정판부터 C++ 언어는 ''람다 표현식''이라고 하는 특수한 언어 구성 요소로부터 자동으로 생성되는 함수 객체의 일종인 클로저도 지원한다. C++ 클로저는 접근된 변수의 복사본을 클로저 객체의 멤버로 저장하거나 참조로 저장하여 컨텍스트를 캡처할 수 있다. 후자의 경우, 클로저 객체가 참조된 객체의 범위를 벗어나는 경우, `operator()`를 호출하면 정의되지 않은 동작이 발생하는데, 이는 C++ 클로저가 컨텍스트의 수명을 연장하지 않기 때문이다.



void foo(string myname) {

int y;

vector n;

// ...

auto i = std::find_if(n.begin(), n.end(),

// 람다 표현식:

[&](const string& s) { return s != myname && s.size() > y; }

);

// 'i'는 이제 'n.end()'이거나 'n'에서 'myname'과 같지 않고 길이가 'y'보다 큰 첫 번째 문자열을 가리킨다.

}



C++11 표준 이후 람다식을 사용할 수 있게 되었다. 다음처럼 지역 변수의 캡처 방법을 제어할 수 있다. 자세한 내용은 C++11을 참조하라.



#include

#include

#include

#include

void foo(std::string s) {

int n = 0;

// 모든 자유 변수를 값 캡처.

auto func1 = [=]() { std::cout << n << ", " << s << std::endl; };

n = 1;

s = "";

func1();

// 모든 자유 변수를 참조 캡처.

auto func2 = [&]() { n = -1; s = "hoge"; };

func2();

std::cout << n << ", " << s << std::endl;

}

bool findName(const std::vector& v, const std::string& name) {

// 이름을 지정하여 자유 변수를 참조 캡처.

auto it = std::find_if(v.begin(), v.end(), [&name](const std::string& s) { return s == name; });

return it != v.end();

}



C 언어에서는 콜백을 지원하는 라이브러리 함수 중에, 함수 포인터와 부가적인 임의의 데이터를 가리키는 포인터 (예를 들어 범용 포인터인 `void*` 등)라는 두 개의 값을 받는 경우가 있다.

```c

typedef int CallbackFunctionType(void* userData);

extern int callUserFunction(CallbackFunctionType* callbackFunction, void* userData);

```

라이브러리 함수 `callUserFunction`이 콜백 함수 `callbackFunction`을 실행할 때마다, 실행 컨텍스트로 데이터 포인터 `userData`를 사용한다. 이것으로 콜백은 상태를 관리할 수 있으며, 등록한 임의의 정보를 참조할 수 있다. 이 관용구는 클로저와 기능 면에서 유사하지만, 구문 면에서는 유사하지 않다.

C++(C++)에서는 `operator()`를 오버로드한 클래스(또는 구조체)를 통해 함수 객체를 정의할 수 있다. 이는 함수형 언어의 함수와 다소 유사한 동작을 보인다. C++의 함수 객체는 비정적 멤버 변수를 통해 상태를 가질 수도 있다. 그러나 일반적인 클로저처럼 자동으로 (암묵적으로) 지역 변수를 캡처하지는 않는다.

C++11 이전에는 함수 내에서 클래스를 정의하는 지역 클래스를 템플릿 형식 인수로 전달할 수 없었고, 암묵적으로 참조 가능한 외부 지역 변수는 static 변수뿐이었다. 자유 변수 캡처를 모방하려면 함수 객체의 비정적 멤버 변수로 명시적으로 저장해야 했다. 이는 Java의 익명 클래스보다 더 많은 제약 조건이었다. C++11 이후의 람다식은 컴파일러에 의해 내부적으로 함수 객체를 자동 생성하여 실현된다. 따라서 자유 변수를 캡처할 때는 함수 객체든 람다식이든 변수 수명에 유의해야 한다.

7. 3. Java

java

class CalculationWindow extends JFrame {

private volatile int result;

// ...

public void calculateInSeparateThread(final URI uri) {

// "new Runnable() { ... }" 표현식은 'Runnable' 인터페이스를 구현하는 익명 클래스입니다.

new Thread(

new Runnable() {

void run() {

// final 로컬 변수를 읽을 수 있습니다:

calculate(uri);

// 둘러싸는 클래스의 private 필드에 액세스할 수 있습니다:

result = result + 10;

}

}

).start();

}

}

```

```java

class CalculationWindow extends JFrame {

private volatile int result;

// ...

public void calculateInSeparateThread(final URI uri) {

// () -> { /* code */ } 코드는 클로저입니다.

new Thread(() -> {

calculate(uri);

result = result + 10;

}).start();

}

}

```

자바 7 이전에는 메서드 내부에 "로컬 클래스" 또는 "익명 클래스"[28]를 정의하여 클로저와 유사한 기능을 구현할 수 있었다. 로컬 클래스/익명 클래스에서는 해당 메서드의 `final`(읽기 전용) 로컬 변수를, 로컬 클래스/익명 클래스의 필드와 이름이 충돌하지 않는 한, 참조할 수 있다.

`final` 변수의 캡처는 값으로 변수를 캡처할 수 있도록 한다. 캡처할 변수가 non-`final`인 경우에도 클래스 바로 전에 임시 `final` 변수로 복사할 수 있다.

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

Java 8의 람다 표현식[16]이 등장하면서, 클로저를 지원한다.

7. 4. C#, VB.NET, D

C#과 Visual Basic .NET은 익명 메서드와 람다식을 통해 클로저를 지원한다. 다음은 C# 코드 예제이다.

```csharp

var data = new[] {1, 2, 3, 4};

var multiplier = 2;

var result = data.Select(x => x * multiplier);

```

다음은 Visual Basic .NET의 코드 예제이다.

```vb.net

Dim data = {1, 2, 3, 4}

Dim multiplier = 2

Dim result = data.Select(Function(x) x * multiplier)

```

D는 델리게이트를 통해 클로저를 구현하며, 델리게이트는 컨텍스트 포인터(예: 클래스 인스턴스 또는 클로저의 경우 힙의 스택 프레임)와 쌍을 이루는 함수 포인터이다.

```D

auto test1() {

int a = 7;

return delegate() { return a + 3; }; // 익명 델리게이트 생성

}

auto test2() {

int a = 20;

int foo() { return a + 5; } // 내부 함수

return &foo; // 델리게이트를 생성하는 다른 방법

}

void bar() {

auto dg = test1();

dg(); // =10 // ok, test1.a는 클로저 안에 있으며 여전히 존재한다

dg = test2();

dg(); // =25 // ok, test2.a는 클로저 안에 있으며 여전히 존재한다

}

```

D 버전 1은 클로저 지원이 제한적이었다. 예를 들어 위의 코드는 제대로 작동하지 않는데, 변수 a가 스택에 있기 때문에 test()에서 반환된 후에는 더 이상 유효하지 않아 사용이 불가능하기 때문이다. 이 문제는 변수 'a'를 명시적으로 힙에 할당하거나, 구조체 또는 클래스를 사용하여 필요한 모든 닫힌 변수를 저장하고 동일한 코드를 구현하는 메서드에서 델리게이트를 구성하여 해결할 수 있었다.

이러한 제한은 D 버전 2에서 수정되었다. 변수 'a'는 내부 함수에서 사용되므로 자동으로 힙에 할당되며, 해당 함수의 델리게이트는 현재 범위를 벗어날 수 있다.

7. 5. Eiffel

아이펠은 인라인 에이전트를 통해 클로저를 정의한다. 인라인 에이전트는 루틴을 나타내는 객체로, 루틴의 코드를 인라인으로 제공한다. 예를 들어 다음과 같다.

```eiffel

ok_button.click_event.subscribe (

agent (x, y: INTEGER) do

map.country_at_coordinates (x, y).display

end

)

```

위 코드에서 `subscribe`에 대한 인수는 두 개의 인수를 가진 프로시저를 나타내는 에이전트이다. 이 프로시저는 해당 좌표에서 국가를 찾아 표시한다. 전체 에이전트는 특정 버튼의 이벤트 유형 `click_event`에 "구독"된다. 사용자가 버튼을 클릭하면 해당 버튼에서 이벤트 유형의 인스턴스가 발생하고, 프로시저가 `x`와 `y`에 대한 인수로 마우스 좌표를 전달받아 실행된다.

아이펠 에이전트의 주요 제한 사항은 둘러싸는 범위의 지역 변수를 참조할 수 없다는 것이다. 이러한 설계는 클로저의 지역 변수 값에 대해 이야기할 때 발생할 수 있는 모호성을 피하기 위함이다. 즉, 변수의 최신 값을 참조해야 하는지, 아니면 에이전트가 생성될 때 캡처된 값을 참조해야 하는지에 대한 모호성이다. `Current`(Java의 `this`와 유사한 현재 객체에 대한 참조), 해당 기능 및 에이전트의 인수만 에이전트 본체 내에서 액세스할 수 있다. 외부 지역 변수의 값은 에이전트에 추가로 닫힌 피연산자를 제공하여 전달할 수 있다.

7. 6. Objective-C

애플은 C, C++, Objective-C 및 Mac OS X 10.6 "Snow Leopard"와 iOS 4.0에 대한 비표준 확장 형태로 블록을 도입했다.[20][21] 블록은 클로저의 한 형태이다. 애플은 GCC 및 clang 컴파일러에 대해 자체 구현을 사용할 수 있도록 했다.

블록에 대한 포인터와 블록 리터럴은 `^`로 표시된다. 일반 지역 변수는 블록이 생성될 때 값으로 캡처되며 블록 내부에서 읽기 전용이다. 참조로 캡처할 변수는 `__block`으로 표시된다. 생성된 범위 외부에서 유지되어야 하는 블록은 복사해야 할 수 있다.

```objc

typedef int (^IntBlock)();

IntBlock downCounter(int start) {

__block int i = start;

return

return i--;

} copy] autorelease];

}

IntBlock f = downCounter(5);

NSLog(@"%d", f());

NSLog(@"%d", f());

NSLog(@"%d", f());

8. 구현

클로저는 일반적으로 함수 코드에 대한 포인터와 함수 생성 시점의 렉시컬 환경(사용 가능한 변수 및 해당 값의 집합)을 표현하는 자료 구조를 사용하여 구현된다.[2] 참조 환경은 클로저가 생성될 때 비지역 이름을 렉시컬 환경의 해당 변수에 바인딩하고, 클로저의 수명만큼 연장한다. 나중에 클로저가 다른 렉시컬 환경으로 들어갈 때, 함수는 현재 환경이 아닌 클로저에 의해 캡처된 비지역 변수를 참조하여 실행된다.

모든 자동 변수를 선형 스택에 할당하는 런타임 메모리 모델을 가진 언어에서는 완전한 클로저를 쉽게 지원할 수 없다. 이러한 언어에서는 함수가 반환될 때 함수의 자동 지역 변수가 해제된다. 그러나 클로저는 참조하는 자유 변수가 포함하는 함수의 실행보다 오래 지속되어야 한다. 따라서 이러한 변수는 힙 할당을 통해 스택이 아닌, 더 이상 필요하지 않을 때까지 지속되도록 할당해야 하며, 해당 변수를 참조하는 모든 클로저가 더 이상 사용되지 않을 때까지 수명을 관리해야 한다.

이는 일반적으로 클로저를 기본적으로 지원하는 언어가 가비지 컬렉션도 사용하는 이유를 설명한다. 대안은 비지역 변수의 수동 메모리 관리(명시적으로 힙에 할당하고 완료 시 해제)를 하는 것이다. 또는 스택 할당을 사용하는 경우, C++11의 람다 표현식[10] 또는 GNU C의 중첩 함수와 같이, 언어가 특정 사용 사례가 해제된 자동 변수에 대한 댕글링 포인터로 인해 정의되지 않은 동작을 초래한다는 것을 허용하는 것이다.[11]

동시 프로그래밍 언어에서 클로저 변수의 업데이트 및 동기화 문제는 중요한 문제이며, 액터 모델은 이에 대한 한 가지 솔루션을 제공한다.[12]

클로저는 함수 객체와 밀접하게 관련되어 있으며, 전자를 후자로 변환하는 것을 비기능화 또는 람다 리프팅이라고 한다.

참조

[1] 문서 Scheme: An interpreter for extended lambda calculus
[2] 간행물 Some History of Functional Programming Languages http://www.cs.kent.a[...] Springer
[3] 논문 The mechanical evaluation of expressions https://academic.oup[...] 1964-01
[4] 논문 The Function of FUNCTION in LISP, or Why the FUNARG Problem Should Be Called the Environment Problem 1970-06
[5] 서적 Functional Programming using Standard ML Prentice Hall
[6] 보고서 Scheme: An Interpreter for the Extended Lambda Calculus 1975-12
[7] 서적 Structure and Interpretation of Computer Programs https://mitpress.mit[...] MIT Press 1996
[8] 웹사이트 array.filter https://developer.mo[...] 2010-01-10
[9] 웹사이트 Re: FP, OO and relations. Does anyone trump the others? http://okmij.org/ftp[...] 2008-12-23
[10] 문서 Lambda Expressions and Closures http://www.open-std.[...] C++ Standards Committee 2008-02-29
[11] 웹사이트 6.4 Nested Functions https://gcc.gnu.org/[...]
[12] 문서 Foundations of Actor Semantics https://dspace.mit.e[...] MIT Mathematics 1981-06
[13] 웹사이트 Function.prototype.bind() https://developer.mo[...] 2018-11-20
[14] 웹사이트 Closures https://developer.mo[...] 2018-11-20
[15] 웹사이트 Nested functions https://gcc.gnu.org/[...]
[16] 웹사이트 Lambda Expressions http://docs.oracle.c[...]
[17] 웹사이트 Nested, Inner, Member, and Top-Level Classes https://blogs.oracle[...] 2007-07
[18] 웹사이트 Inner Class Example https://java.sun.com[...]
[19] 웹사이트 Nested Classes https://java.sun.com[...]
[20] 웹사이트 Blocks Programming Topics https://developer.ap[...] Apple Inc. 2011-03-08
[21] 웹사이트 Programming with C Blocks on Apple Devices http://thirdcog.eu/p[...] 2010-09-18
[22] 문서 Closure http://docwiki.embar[...]
[23] 웹사이트 クロージャ - JavaScript | MDN https://developer.mo[...]
[24] 웹사이트 From LISP 1 to LISP 1.5 https://www-formal.s[...] 2024-04-07
[25] 논문 Shallow binding in Lisp 1.5 https://doi.org/10.1[...] Association for Computing Machinery 1978-07
[26] 보고서 Scheme: An Interpreter for Extended Lambda Calculus https://dspace.mit.e[...]
[27] 논문 Scheme: A interpreter for extended lambda calculus https://www.research[...] Springer
[28] 문서
[29] 문서 Closures (Lambda Expressions) for the Java Programming Language http://www.javac.inf[...]
[30] 문서
[31] 웹사이트 Closures: Anonymous Functions that Capture Their Environment, Capturing References or Moving Ownership https://doc.rust-lan[...] 2023-12-24
[32] 웹사이트 Module std::pin https://doc.rust-lan[...] 2023-12-24



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com