ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 람다함수, 람다식 그리고 클로저..
    카테고리 없음 2016. 10. 14. 14:43
    [참고]


    람다대수 (Lambda calculus)


    현재 프로그래밍 언어에서 사용되고 있는 람다에 대하여 깊은 이해를 하고자 한다면 역시 이론적인 배경을 한 번쯤 짚어보는 것도 좋겠다. 현재 사용되고 있는 람다의 근간은 수학과 기초 컴퓨터과학 분야에서의 람다 대수이다. 람다 대수는 간단히 말하자면 수학에서 사용하는 함수를 보다 단순하게 표현하는 방법이다.


    람다 대수는 다음과 같은 특징이 있다.

    1. 람다 대수는 이름을 가질 필요가 없다. (익명 함수)
    2. 두 개 이상의 입력이 있는 함수는 최종적으로 1개의 입력만 받는 람다 대수로 단순화 될 수 있다. (커링)



    익명함수 (Anonymous function)


    익명 함수는 람다 대수로부터 영향을 받아 만들어진 프로그래밍에서 함수를 표현하는 방식의 일종이다. 비교적 최근부터 대부분의 프로그래밍 언어가 익명 함수를 지원한다. 흔히 Java가 8버전 부터 람다를 지원하게 되었다고 하는데 이때 람다가 익명 함수(Lambda abstraction)를 말한다. 


    각 언어별로 익명 함수를 표현하는 법은 제각기 다르지만, 공통적으로 가지는 특징이 있다. 바로 일급 객체(First-class citizen) 라는 점이다.


    일급 객체는 함수의 인자로 넘겨받을 수도 있으며, 함수의 결과값으로 티너할 수도 있고, 변수에 값을 할당할 수도 있다는 것을 말한다. 프로그래밍 언어에서 익명 핫무는 애초에 다른 함수에 인자로 넘기거나 함수의 결과 값으로 리턴할 용도로 만들어지기 때문에 이런 특징을 갖게 되는 것이다. 또한 함수가 일급 객체가 되면 일급 함수(First-class function)라고 부를 수 있다. 


    나는 익명함수이면서 일급함수라면 람다라고 부를 수 있다고 생각한다. 어떤 분은 람다는 순수함수여야 한다는 생각을 가지고 있기는 하지만, 이것은 위에서 다룬 수학적 의미의 람다에 가까운 것으로 프로그래밍 언어에서의 람다와는 거리가 있다는 게 내 생각이다. 물론 함수형 프로그래밍 패러다임에서는 순수함수여야겠지만 함수형 프로그래밍 패러다임에서는 람다가 아니라 모든 함수가 순수함수여야 한다. 


    아까 전에 각 언어별로 익명 함수를 표현하는 법이 제각기 다르다고 했는데, 그 차이가 가장 두드러지는 언어가 파이썬이다. 파이썬의 익명함수는 문맥(Statement)이 아닌 표현식( Expression)만을 지원한다. 즉 파이썬의 익명함수는 블록을 가지지도 않고, 리턴한느 값만 표현하는 식 하나로만 구성된다는 것이다. Quora의 어떤 질문의 답변에서 파이썬은 람다 함수(Lambda function)가 아니라 람다 표현식(Lambda expression)을 지원한느 것이라고 하는데 아주 적절한 표현이라고 생각한다. 



    JavaScript의 익명 함수


    위의 정의에 의하면 JavaScript의 모든 함수는 일급 객체이므로 JavaScript에서는 익명 함수이기만 하면 람다라고 볼수 있겠다. 이렇게만 보면 사실 전혀 특별한 점이 없다. 왜냐하면 JavaScript에서 익명 함수는 일상적으로 사용하는 것이기 때문이다. 


    가장 흔한 용례로는 콜백을 꼽지 않을 수 없다. 콜백으로 Named Function을 넘길 수도 있지만, 대개의 경우 일회용 함수를 넘기기 위해서 익명 함수를 사용하게 된다. 클로저도 마찬가지로 익명함수를 사용한다. 너무나 많은 용례가 있어 굳이 하낳나 꼽을 필요도 없을 것 같다. 


    ES2015에서는 Arrow function이 도입되면서 익명함수를 보다 람다스럽게(…) 표현할 수 있게 되었는데, 이부분 문법 변화만 짚고 넘어가려고 한다.


    먼저 ES5의 람다 식이다. 


    Arrow function을 사용하면 다음과 같이 매우 간결하게 표현이 가능하다.




    람다와 클로저(Closure) 


    클로저는 람다로부터 파생된 개념이다 다음의 예제를 보자 


    add5 라는 함수의 입장에서 생각해볼 때, 자신의 스코프 내에 있는 b라는 변수는 인자로 받은 변수이고 해당 스코프 내에 갇혀있지만, a라는 변수는 대체 어디서 와서 사용되고 있는지 알 수가 없다. 이때의 a를 자유 변수(Free variable), b를 묶인 변수(Bound variable)라고 부른다.


    위의 람다식에서는 자유 변수와 묶인 변수를 하나씩 사용하고 있다. 람다식은 사용하는 변수의 종류에 따라 두 종류로 나눌 수 있다. 바로 닫힌 람다식(Closed expression)과 열린 람다식(Open expression)이다.


    람다 표현식에서 사용하는 변수들이 모두 묶인 변수일 때 닫힌 람다식이라고 부른다. 그리고 람다 표현식에서 사용하는 변수들 중 하나라도 자유 변수가 있을 때 열린 람다식이라고 부른다.


    자, 이제 클로저를 아주 간단하게 설명할 수 있따. 클로저는 바로 열린 람다식을 닫힌 람다식으로 만드는 것이다.

    클로저의 이름이 어떻게 유래되었는지도 예상이 될 것이다. 클로저는 람다식 내의 모든 자유 변수를 스코프 내로 가져와 묶는다. 그렇기 때문에 클로저는 만들어진 환경을 기억하는 것처럼 보인다.




    JavaScript의 클로저


     


     위의 예제는 앞서 설명했던 케이스에 글로벌 변수인 a가 추가되었다. 그런데 클로저의 입장에서 보면 b도 자유 변수이고, a도 자유 변수이므로 똑같이 묶어야 할 것으로 보인다. 하지만 위에서 본 대로 실제 동작은 그렇지 않다. 








    개요


    람다식, 또는 람다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어이다. 프로그래밍 언어학적으로 파고들면 이것만 한 달 이상 배우는 경우도 많으며, 실제로 여러 대학교들에서 사용하는 프로그래밍 언어 교재에서도 꽤나 많은 분량을 차지하는 개념이지만 실무적으로는 코드의 간결함, 지연 연산을 통한 퍼포먼스 향상, 그리고 기존 이터레이션 관련 코드를 구현하는 데 있어 불필요한 부분들을 제거할 수 있다는 점에서 비교적 중요하게 다루어지고 있다. 이것을 활용한 프로그래밍 기법이 클로저 (Closure)다.



    장점


    1. 코드의 간결성 : 효율적인 람다 함수의 사용을 통하여 불필요한 루프문의 삭제가 가능하며, 동일한 함수를 재활용할 수 있는 여지가 커진다.
    2. 필요한 정보만을 사용하는 방식을 통한 퍼포먼스 향상 : 지연 연산을 지원하는 방식을 통하여 효율적인 퍼포먼스를 기대할 수 있다. 이 경우 메모리 상의 효율성 및 불필요한 연산의 배제가 가능하다는 장점이 있다.



    단점


    1. 어떤 방법으로 작성해도 모든 원소를 전부 순회하는 경우는 람다식이 조금 느릴 수 밖에 없다. ( 어떤 방법으로 만들어도 최종 출력되는 bytecode 나 어셈블리 코드는 단순 while(혹은 for) 문 보다 몇 단계를 더 거치게 된다)
    2. 익명함수의 특성상 함수 외부의 캡처를 하는 시간제약 논리제약적인 요소도 고려해야한다.
    3. 람다식을 너무 남발하여 사용하게 되면 오히려 코드를 이해하기 어려울 수도 있다.



    유의사항 


    모든 언어에서 제공되지는 않는다: 대부분의 유명한 언어들은 지원하지만, 지원하지 않는 언어도 가끔씩 있다. 특히 고전적인 문법들의 경우 거의 모든 언어에서 제공됨을 보장할 수 있는 부분과는 차별된다. 대표적으로 C, Fortlan, Pascal 등이 지원하지 않는 언어. Java의 경우 8부터 지원하며, C++은  C++11부터 지원한다.

    .Net Framework는 이미 2.0부터 대리자, 메서드 참조, 제너릭을 통해 비슷하게나마 지원하고 있었지만, 본격적으로 람다식이 지원되기 시작한 건 LINQ가 추가된 3.5부터이다. 그래ㅈ봤자 대부분은 굳이 람다식을 쓰지 않고도 사용할 수 있기에 큰 의미는 없다.


    Java 8 람다 예시


    IntStream.rang(0, 10).forEach((int value) -> System.out.println(value));



Designed by Tistory.