원문 - http://www.martinfowler.com/bliki/Closure.html

클로저 (Closure)          마틴 파울러 (Martin Fowler)

동적 언어에 대한 관심이 커짐에 따라 많은 사람들이 클로저 (Closure) 혹은 블록 (Block) 이라는 프로그래밍 개념을 접하게 되었다. 그런데, 클로저를 지원하지 않는 C/C++/Java/C#  같은 언어들에 익숙한 사람들은 그게 무엇인지 잘 알지 못한다. Here's a brief explanation, those who done a decent amount of programming in languages that have them won't find this interesting.

클로저란 개념은 오래전부터 여러 프로그래밍 언어들 속에 있었다. 스몰토크(Smalltalk) 에서는 블록이라는 이름으로 불리는데, 필자는 이 스몰토크를 통해 처음으로 클로저를 사용했다. Lisp 은 클로저가 비중있게 사용되는 언어이다. 또한 스크립트 언어인 루비 (Ruby) 에서도 클로저가 사용이 되는데, 이는 프로그래머들이 루비를 좋아하는 가장 큰 이유이기도 하다.

기본적으로 클로저란 함수 호출시 인자로 전달 될 수 있는 코드 블록을 말한다. 간단한 예를 들어 설명하겠다. 직원 (employee) 객체들의 리스트가 있을때, 이 리스트에서 관리자 (manager) 객체들의 리스트만 얻고자 한다. 직원 객체가 관리자 객체인지 판별하는 메소드는 IsManager 라고 하자. C# 으로는 다음과 같이 쓸수 있다.

public static IList Managers(IList emps) {
IList result = new ArrayList();
foreach(Employee e in emps)
if (e.IsManager) result.Add(e);
return result;
}

그런데, 클로저를 지원하는 언어(여기서는 루비를 사용한다) 사용하면,  다음과 같이 쓸수 있다.

def managers(emps)
return emps.select {|e| e.isManager}
end

select 는 루비의 컬렉션 클래스에서 정의되는 메소드이다. select 메소드는 코드 블록, 즉 클로저를 인자로 받는다. 루비에서는 이 코드 블록을 괄호사이에 작성한다. (다른 방법도 있긴 하다.) 이 코드 블록이 인자를 필요로 한다면, 인자를 수직 바 '|' 사이에 선언해주면 된다. select 가 하는 일은 입력 배열의 각 원소들에 대해 이 코드 블록을 실행하고, 실행 결과가 참인 원소들의 리스트를 반환하는 것이다.

이쯤해서, 만약 당신이 C 프로그래머라면 아마도 "함수 포인터를 이용해서 똑같이 할 수 있어" 라고 생각할지도 모른다.  Java 프로그래머라면 "익명 내부 클래스를 이용해서 똑같이 할 수 있어" 라고 생각할지 모른다. C# 프로그래머라면 위임 (Delegate) 를 고려해 볼 것이다. 이런 방식들은 클로저와 유사하긴 하지만 2가지 점에서 클로저와 다르다.

첫번째는 형식적인 차이다. 클로저는 그것이 처음 정의되는 시점에서 볼수 있는 변수들을 참조할 수 있다. 다음 메소드를 보자.

def highPaid(emps)
threshold = 150
return emps.select {|e| e.salary > threshold}
end

select 내의 코드 블록이 메소드의 지역 변수를 참조하고 있음을 눈여겨 보길 바란다. 진정한 클로저를 지원하지않는 언어들에서 사용되는 클로저와 비슷한 기능 들 (이를테면 앞서 얘기한 함수 포인터나 익명 내부 클래스, 위임 등, 역자 주) 은 불가능한 일이다. 클로저는 이보다 더 흥미로운 일을 할 수 있도록 해준다. 다음 함수를 보자.

def paidMore(amount)
return Proc.new {|e| e.salary > amount}
end

이 함수는 클로저를 리턴한다. 그렇다. 내부로 전달된 인자에 의해 동작이 결정되는 클로저를 리턴한다. 이런 함수를 만들고 이를 변수에 할당하는게 가능하다.

highPaid = paidMore(150)

highPaid 변수는 150이상의 급여를 갖는 객체를 테스트하는 코드 블록 (루비의 Proc 에 의해 만들어진) 을 갖게 된다. 이를 가지고 다음과 같이 사용할 수 있을 것이다.

john = Employee.new
john.salary = 200
print highPaid.call(john)


highPaid.call (john) 이라는 문장은 앞서 정의한 대로 e.salary > amount 라는 코드를 호출한다. 여기서 amount 변수는 proc 객체를 생성할 때 인자로 전달한 150 이라는 값을 갖고 있다. print 호출이 일어나는 지점은 150 이라는 값의 범위 (Scope) 에서 벗어나지만, 여전히 바인딩은 지속되고 있다.

이처럼, 클로저의 첫번째 중요한 점은 클로저는 처음 만들어진 환경에서의 바인딩이 더해진 코드 블록이라는 것이다. 이는 클로저가 함수 포인터나 다른 유사한 기술들 (Java 의 익명 클래스도 지역 변수에 접근할 수 있지만 오직 final 일 때만 가능하다.) 과 구별되도록 해주는 형식적인 부분이다.

두번째 차이점은 형식적인 차이점보다는 덜 실용적이다는 점에서  작지만 그래도 여전히 중요하다. 클로저를 지원하는 언어들은 매우 적은 문법으로 그것들을 정의할 수 있도록 해준다. 그다지 중요하지 않게 보일지 모르지만 나는 중요하다고 믿는다. 이와 같은 단순함이 클로저를 자주 사용할 수 있도록 해주는 것이다. Lisp 과 스몰토크, 루비를 보자. 다른 언어에서 그와 유사한 구조들 보다 더욱 자주 클로저가 사용되는 것을 볼 수 있다. 지역 변수바인딩 기능은 클로저가 자주 사용되는 이유 중 하나이지만 그보다 더 큰 이유는 바로 단순하고 명료한 문법때문이다.

이전의 스몰토크 프로그래머들이 자바를 사용하기 시작했을때 일어난 일들은 이에 대한 좋은 예다. 처음엔 필자를 포함해서 많은 사람들이 스몰토크에서 블록으로 해왔던 많은 일들을 자바의 익명 내부 클래스를 사용하여 작업을 했었다. 그러나 그 결과 코드는 매우 어지럽고 보기 흉해서 결국 우리는 그것을 포기했다.

필자는 루비를 가지고 클로저를 자주 사용한다. 그러나, Proc 를 생성하고 그것을 전달하는 방식은 잘 쓰지 않는다. 주로 클로저를 사용하는 용도는 앞서 보여줬던 select 메소드와 유사한 CollectionClosureMethod 를 사용할 때이다. 다른 주요 용도로는 파일을 처리할 때와 같은 '메소드와 함께 실행하기 (excure around method)' 이다.

File.open(filename) {|f| doSomethingWithFile(f)}

여기서 open 메소드는 파일을 열고, 제공된 블록을 실행한 다음, 다시 파일을 닫는다. 이것은 트랜잭션 (커밋이나 롤백을 빼먹지 않도록) 혹은 끝에 무언가를 해야한다는 것을 반드시 기억해야하는 어느 경우에라도 사용될 수 있는 편리한 기법이다. 필자는 XML 변환 루틴을 작성하면서 이 기법을 광범위하게 사용하고 있다.

이와 같은 클로저의 사용은 사실 Lisp 과 기능적 프로그래밍 세계에서 사람들이 사용하는 것에 비해 그 용도가 매우 적은 편이다. 그러나 필자는 이 용도가 제한적임에도 클로저를 지원하지 않는 다른 언어를 가지고 프로그래밍을 할때면 클로저가 매우 아쉽다. 처음 클로저를 접하는 사람은 이게 별것 아닌 것처럼 보여지지만 오래지 않아 클로저를 좋아하게 된다.

다른 언어에서의 예제 들

Joe Walnes 는 C# 다음 버젼의 클로저에 대한 블로그를 남겼다. 정적 타입 언어에서 요구되는 문법을 고려해볼때 C# 의 클로저 지원은 매우 합리적이다. 이는 위임에 기반해 있으며 delegate 키워드를 이용하여 클로저를 구현할 수 있다.

Ivan Moore 는 Python 에서 유사한 예제들을 제공한다.

Boo 언어에 대한 예제도 있다.

출처 : Tong - sasasasacom님의 Programming통


Posted by 인터레스트
,