자바 7은 클로저를 지원할 듯

Posted by 大山 Sun, 20 Aug 2006 06:14:00 GMT

자바 7에서는 드디어 클로저를 지원하려나 보다. 지금이 자바 5이니, 자바 7이 언제 나오게 될지는 모를 일이지만.

모르는 분들을 위해 살짝 귀뜸하면, 클로저(Closure)는 코드 중에 함수를 직접 정의해서 객체로 변환시킨 걸 이르는 용어이다. 우리말로 하면, 익명 함수 정도가 될까? 흥미로운 건 이걸 다른 메소드에 인자로 넘길 수 있다는 거다. 예를 들면, 다음과 같은 식이다.

>> [1, 2, 3, 4, 5].map(&lambda {|i| i * 3})
=> [3, 6, 9, 12, 15]
 

위에서 lambda { ... } 부분이 클로저인데, 이는 i에 해당하는 수를 넘겨주면 그의 3배수를 리턴하는 익명 함수 객체이다. 위의 코드에서는 이 클로저를 배열의 map 메소드에 인자로 넘겨주고 있다. map 메소드는 배열의 각 원소에 넘겨받은 클로져를 적용해서, 새로운 배열을 만들어 리턴해준다. 이를 보다 루비스럽게 작성하면 다음처럼 되겠다.

>> [1, 2, 3, 4, 5].map {|i| i * 3}
=> [3, 6, 9, 12, 15]
 

루비에서는 클로저란 어려운 이름 대신, 이를 블록이라고 부른다. 다른 예를 하나 들어보자.

>> ["banana", "kiwi", "apple", "water melon"].sort
=> ["apple", "banana", "kiwi", "water melon"]
 

위의 코드에서 sort 메소드는 배열의 원소를 알파벳 순서로 정렬해준다. 그런데 sort에 블록을 붙여주면 정렬 법칙을 바꿀 수 있다. 다음 코드를 한번 보자.

>> ["banana", "kiwi", "water melon", "apple"].sort {|i, j| i.length <=> j.length}
=> ["kiwi", "apple", "banana", "water melon"]
 

위에서는 문자열의 길이를 비교하는 블록을 sort 메소드에 넘기고 있다. 결과적으로 리턴되는 배열도 문자열의 길이대로 정렬되어진다.

자바 5에서는 generic이 추가되더니 7에서는 클로져를 지원하는 등 자바도 갈수록 루비스러워지는 듯. 자바 7을 기다리는 그대 지금 루비로 오라. ;)

Posted in  | Tags , , , ,  | 22 comments | 2 trackbacks

Comments

  1. 1.
    홍민희 said 18 minutes later:

    제가 Ruby나 JavaScript 등의 언어에서 가장 부러워하는 것은 클로저(closure)나 익명 함수라고 불리는 함수 리터럴입니다. Python에도 람다(lambda)가 있긴 하지만, 좀 더 긴 서브루틴 작성을 그 자리에서 할 수 있었으면 좋겠네요(def 키워드가 표현식이 되면 좋을텐데).

    map(lambda i: i * 3, [1, 2, 3, 4, 5]) == [i * 3 for i in [1, 2, 3, 4, 5]]


  2. 2.
    홍민희 said 24 minutes later:

    C++의 경우 그 강력한 템플릿 메타프로그래밍(template metaprogramming)을 통해, 언어에서도 지원 안하는 람다를 Boost.Lambda라는 라이브러리로 만들어버렸습니다. 아직까지는 C++이 정적 언어임에도 가장 표현럭 강한 DSEL 구현이 가능한 언어인 것 같습니다.

    int x[5] = {1, 2, 3, 4, 5};
    transform(x, x[5], x, _1 * 3);

  3. 3.
    大山 said about 1 hour later:

    @홍민희: C++의 런타임 해석기 기능, Lisp의 매크로 기능, 루비나 Python의 eval 함수, 어떤게 최선일까요? 강력하기는 Lisp의 매크로를 따라갈게 없겠지만, 일상적으로 사용하기에는 eval 함수가 편리하다는 생각입니다~ :)


  4. 4.
    홍민희 said about 1 hour later:

    글쎄, 효율의 떨어짐 없는 추상화—일반화 프로그래밍(generic programming)의 특징 가운데 하나이죠—까지 살펴보면 C++도 분명 대단한 언어임에 틀림 없습니다. 그런데 C++의 런타임 해석이라는 것은 무엇을 말하시는 건가요?


  5. 5.
    大山 said about 2 hours later:

    @홍민희: 효율의 떨어짐이 없는 추상화라.. 멋진 표현이네요~ ^^ C++만의 효율성이 중요한 분야도 많이 있지요. 다만 지금은 제가 루비에 스포일된 상황이라서, 효율성은 중요치 않다고 자기최면을 걸로 있답니다~ ;) 루비에서는 부분적 최적화가 필요한 경우에는 아예 모듈 자체를 C로 개발하고 있지요.

    위에서 말씀드린 부분은 메타프로그래밍을 구현하는 방식입니다. Lisp에서는 신택스 레벨에서 매크로를 사용해서 메타프로그래밍을 지원하고, 루비에서는 eval 함수로 문자열을 해석하는 방식으로 메타프로그래밍을 지원하지요. C++의 템플릿 프로그래밍은 컴파일러에서 런타임 해석기를 구현해주는 방식인 것으로 이해하고 있습니다.


  6. 6.
    홍민희 said about 2 hours later:

    아하, 그런 말씀이셨군요. 컴파일 타임에 인스턴스화(instantiate)되는 템플릿의 특징 덕분에 C++의 템플릿 메타프로그래밍의 DSEL 코드는 컴파일 타임에 적절한 코드로 인라인화 됩니다. ㅎㅎ 저는 메타프로그래밍에서의 강력함때문에 스태틱 타입 언어 가운데서는 유일하게 C++를 사랑합니다. ㅋ

    물론 Lisp이나 Ruby, Python 등의 언어 역시 DSEL하기에는 좋은 프로그래밍 언어입니다. 반면 PHP는 저주스럽습니다(그 대안 언어를 심심해서 구상해본 것이 있긴 합니다).

    덧붙여서, 메타프로그래밍은 “프로그램을 만드는 프로그램을 만드는 것”이라고 알고 있습니다. 따라서 컴파일러나 간단한 인터프리터 제작 같은 것들도 메타프로그래밍입니다. 말씀하신 것은 DSEL이라고 표현하는 것이 더 명확할 것 같네요.


  7. 7.
    大山 said about 4 hours later:

    아, 저는 메타프로그래밍을 내장형(Embedded) DSL을 구현하는 방법이란 의미로 주로 사용합니다. 물론 맥락에 따라 코드를 작성하는 코드를 짜는 행위, 런타임에 동적으로 코드를 변경해 주는 프로그래밍 기법 등으로 쓰기도 하구요.

    외장형(External) DSL에 저는 보통 코드 생성(Code Generation)이란 용어를 사용합니다만, 커뮤니티에 따라 용어의 사용은 조금씩 차이가 나겠지요~ :)


  8. 8.
    CN said about 20 hours later:

    저는 C++의 컴파일 타임 능력과 Lisp의 매크로를 선호합니다. 최근에는 Ruby도 써보고 있구요.

    Java에 Closure가 들어온 것은 환영할만하네요. :-)


  9. 9.
    大山 said 1 day later:

    @홍민희, CN: 아, C++의 템플릿 해석기는 런타임에 돌아가는게 아니라 컴파일 타임에 돌아가는 것이군요. 지금껏 제가 잘못 이해하고 있었네요.

    저는 C++에서 메타프로그래밍을 지원한다고 해서, 해석기가 런타임에서 실행된다고 생각을 했었습니다. 그렇다면 C++ 템플릿 프로그래밍은 컴파일러 레벨에서 돌아가는 매크로 전처리기라고 이해하면 될까요?


  10. 10.
    大山 said 1 day later:

    @CN: 자바가 끊임없이 변화하는 것은 칭찬을 해주어야 겠지만, 신택스가 너무 복잡해 지는게 아닌가 하는 우려도 듭니다. 아무래도 상위 호환성 문제 때문에 신택스 체계를 새로 잡는 것은 무리겠지요? :)


  11. 11.
    mkseo said 3 days later:

    아. 자바에 들어가는 closure에 대한 글을 읽은게 저뿐만은 아니군요. 왠지 주위에 비슷한 시기에 최신 소식을 열심히 접하고 있는 사람이 있다니 다행스럽네요. ㅎㅎ

    다만 자바에 들어갈 closure문법은 너무 지저분한데다가, 정작 제안한 사람도 블로그에서 closure에 대해 설명을 하기는하는데 뭔가 포인트를 놓치고 이상한 맥락에서 - 가령 성능! - 설명하려고 해서 많은 불만을 갖고 있습니다.

    C++의 메타프로그래밍은 컴파일타임에 실행되어 코드를 생성해내는 형태로 작동합니다. 그런 이유로 모든 것이 static binding이죠. C++ guru들 같이 '난 virtual이 싫어. 왜냐면 동적 디스패치를 위한 테이블이 있어야하고 그 테이블을 룩업하는데 그러며 느리잖아!' 라는 식으로까지 성능에 신경쓰는 사람들에게는 좋은 해결책이죠.

    문제는 C++의 메타프로그래밍의 작성은 너무 어렵다는 것인데, 다행히 boost와 같은 좋은 라이브러리에서 왠간한건 다 구현하고 있을 뿐만 아니라, 메타프로그래밍 자체를 위한 라이브러리까지 만들어 내고 있습니다.

    전 C++을 궁극의 언어라고 생각하고, TDD는 완전한 해결책이 아니라고 생각하는 편입니다. 하지만 루비가 동적인 특성때문에 IDE에서 autocompletion 이 힘든 것처럼(저는 루비같은 언어에서 완전한 autocompletion을 해내는건 박사급 논문 주제라고 보고 있습니다), C++ 역시 복잡한 파싱 규칙때문에 쓸만한 refactoring 도구 하나 없죠...

    C++0x 나오면 판세가 바뀝니다. ㅎㅎ 너무 미워하지마세요~


  12. 12.
    大山 said 3 days later:

    @mkseo: 그렇군요. 하지만 C++의 메타프로그래밍이 런타임이 아닌 컴파일타임에 일어난다는 것에는 조금 실망입니다. 저는 갈수록 Lisp의 자유분방함에 매료되는 것 같네요~ :)


  13. 13.
    mkseo said 3 days later:

    컴파일타임이라는 것은 언어적인 한계가 한가지 이유이고, 성능이 또다른 이유입니다. 템플릿만 잘 쓰면 virtual 없이도 폴리모픽하게 얼마든지 짤 수 있죠..

    하지만 C++ 의 문제는 이게 아니라, 언어 자체가 지금도 너무 어렵고, 앞으로는 더 어려워질 것이란 점이죠.... 가끔 C++ 코드 쓰다보면 저절로 욕이 나옵니다. ㅎㅎ


  14. 14.
    공성식 said 3 days later:

    안녕하세요?
    좋은 글 잘 읽었습니다.

    그런데 closure에 관한 정의가 좀 부정확한 것 같아 지적하고자 합니다.

    closure는 lambda나 block과 의미면에서 좀 틀립니다. 루비에서는 block에 closure 의 기능을 넣었지만 그렇다고 해서 closure와 block을 동일시하면 좀 곤란하다고 생각합니다. 특히 예로 보여주신 코드만 보면 block이 정말 closure인지 아닌지를 확실히 알 수 없습니다.

    closure의 정확한 의미는 lexically scoped code 라고 할 수 있습니다.
    즉, 어떤 코드가 자신의 주변환경을 기억하고 있어야 합니다. 그런데 보여주신 코드의 경우엔 코드가 주변의 데이터에 접근한 경우가 아니기 때문에 closure인지 아닌지 명확하지가 않습니다.

    closure를 명확히 보여주는 예는 다음과 같습니다.

    def generate_counter start
        lambda {start += 1}
    end
    
    c1 = generate_counter(10)
    c2 = generate_counter(20)
    
    puts c1.call
    puts c2.call
    puts c1.call
    puts c2.call
    puts c1.call
    puts c2.call
    puts c1.call
    puts c2.call
    puts c1.call
    puts c2.call
    
    -----
    11
    21
    12
    22
    13
    23
    14
    24
    15
    25
    -----
    

    위에서는 lambda 에 넘겨주는 블록 내의 로컬변수가 아닌 블록 외부의 변수인 start가 lambda와 함께 저장이 됩니다. 그래서 generate_counter 함수가 종료되도 start 변수가 계속 살아있게 됩니다.

    아마 closure라는 용어가 만들어지게 된 이유도 코드가 주변의 변수와 함께 close되기 때문인 것 같습니다.


  15. 15.
    大山 said 3 days later:

    @공성식: 옳으신 지적입니다. :) 제가 예로 든 코드가 가장 일반적인 형태의 클로저는 아니지요. 하지만 lexical 로컬 변수를 사용하지 않는 블록은 클로저가 아니라는 말씀에는 조금 동의하기 어려운 것 같습니다. 이 경우가 클로저의 특수한 케이스인 것은 사실이지만, 실제로는 이렇게 사용되는 예가 더 많지요. 클로저를 인라인으로 작성하다 보면, lexical 로컬 변수를 사용하는 경우와 그렇지 않은 경우를 구분하여 생각하는 것이 오히려 인위적인 느낌이 든다는 생각입니다만..

    조금 다른 관점에서는 익명 클래스 객체를 온전한 클로저로 사용하는 것도 충분히 가능하답니다. 클래스 생성자에 lexical 로컬 변수를 넘겨주면 되지요.

    제가 이 글에서 사용한 예는 클로저를 처음 접하는 사람들의 이해를 돕기 위해서라고 생각하시면 될 것 같습니다. :)


  16. 16.
    공성식 said 3 days later:

    대산님, 금방 답을 해주셔서 감사합니다.

    ------------
    하지만 lexical 로컬 변수를 사용하지 않는 블록은 클로저가 아니라는 말씀에는 조금 동의하기 어려운 것 같습니다.
    ------------

    이건 좀 오해구요. 로컬 변수만 사용했다고 해도 루비에서는 모든 블럭이 클로저이므로 클로저죠.
    제가 말하고자 했던 것은 만약 루비를 처음 보는 사람에게 로컬 변수만 사용한 블럭을 보여주면서 이게 클로저다 라고 말하면 그냥 코드 블록은 당연히 클로저구나 라고 생각할지 모른다는 의미였습니다. 사실 클로저는 코드 블럭 이상의 의미를 가지고 있는데 말입니다.


  17. 17.
    大山 said 3 days later:

    @공성식: 예, 글을 읽는 사람이 공성식님의 답글을 함께 읽으면 옳바르게 이해하리라 생각합니다. :)


  18. 18.
    mkseo said 3 days later:

    @공성식: closure라는 이름의 어원이 이제 이해가되었습니다. ㅎㅎ 감사..

    참고로 자바의 경우 익명 클래스는 외부의 변수가 final이 아니면 접근할 수 없고, 이는 multithreading에서의 thread safety를 위해서였습니다. 하지만 JAVA7의 closure는 final이 아니어도 접근가능하다는 차이가 있습니다.

    그래서 과거에는 외부 자원을 접근하려면 final로 선언된 레퍼런스를 별도로 하나 만들어야했고, 그러려면 값을 클래스에 저장해놓아야만 했습니다. 그래야만 final 인 참조라도 값의 수정이 가능하니까요.

    이러면에서 java7의 clsoure는 개인적으로 가장 필요하다고 생각했던 기능입니다..


  19. 19.
    大山 said 3 days later:

    @mkseo: 기존의 자바에서는 그런 한계가 있었지요. (왠지 스태틱 언어에는 다소 인위적이라 여겨지는 제약이 많습니다.) 저는 설사 루비의 블록이 lexical 로컬 변수를 사용하지 못한다 하더라도, 온전한 의미의 클로져를 익명 클래스 객체로 구현할 수 있다는 이야기였습니다~ :)

    def generate_counter(start)
      Class.new do
        def initialize(value)
          @value = value
        end
        def call
          @value += 1
        end
      end.new(start) 
    end
    
    c1 = generate_counter(10)
    c2 = generate_counter(20)
    
    puts c1.call
    puts c2.call
    puts c1.call
    puts c2.call
    

  20. 20.
    mkseo said 5 days later:

    @대산: 특별한 거부감이 있으신게 아니라면 이 블로그에 혹시 recent comments 보는 플러그인을 다실 의향은 없으신지요.. 독자로서 찾아읽기가 많이 힘듭니다. (^^)(__)


  21. 21.
    大山 said 5 days later:

    @mkseo: 달았습니다. 그동안 불편하셨다니 죄송합니다~ ^^;;

    그리고 답글에 Markdown 필터도 적용했습니다~


  22. 22.
    mkseo said 5 days later:

    @대산: 이야.. 정말 감사합니다 ^^/ 이제 토론을 쫓아가기가 한결 편할듯 ^^


Trackbacks

Use the following link to trackback from your own site:
http://beyond.daesan.com/articles/trackback/1480

  1. From
    자바 7 클로져: 자바 7엔 클로져가 도입.
    자바 7에 클로져(Closure)가 도입된다 고 한다. 클로져를 모르는 분은 위의 링크를 클릭하면 친절한 설명을 볼 수 있다. 도입되는 형태는 아래의 모양이다. int(int) plus2b = (int x) {return x+2; }; // ...
  2. From
    C++ Template Metaprogramming의 소개
    자바 7은 클로져를 지원할 듯 위 글의 커멘트에서 이야기가 나왔길레 간단하게 써보는 C++ Template Metaprogramming에 대한 소개입니다. C++의 메타프로그래밍은, C++자체가 실행시간 타입에 대한 ...

Comments are disabled