프로그램이 클로저를 사용하는 이유는 무엇입니까? 것입니다. 다음은 그러한 예입니다.

클로저를 설명하는 많은 게시물을 읽은 후에도 여전히 핵심 개념이 누락되었습니다. 왜 클로저를 작성합니까? 클로저가 가장 잘 수행 할 수있는 프로그래머가 수행해야 할 특정 작업은 무엇입니까?

Swift에서 폐쇄의 예는 NSUrl에 액세스하고 리버스 지오 코더를 사용하는 것입니다. 다음은 그러한 예입니다. 불행히도, 이러한 과정은 막을 내립니다. 코드 솔루션이 클로저로 작성된 이유를 설명하지 않습니다.

“aha, 나는 이것에 대한 클로저를 작성해야한다”고 내 두뇌를 자극 할 수 있는 실제 프로그래밍 문제 의 예 는 이론적 인 논의보다 더 유익 할 것이다. 이 사이트에서 이용할 수있는 이론적 인 토론이 부족하지 않습니다.



답변

우선, 클로저를 사용하지 않고는 불가능한 것이 없습니다. 특정 인터페이스를 구현하는 객체로 클로저를 항상 바꿀 수 있습니다. 간결하고 커플 링의 문제 일뿐입니다.

둘째, 클로저는 종종 간단한 함수 참조 나 다른 구조가 더 명확 할 때 부적절하게 사용된다는 것을 명심하십시오. 모범 사례로 간주되는 모든 예를 사용해서는 안됩니다.

고차 함수를 사용할 때, 실제로 상태를 전달 해야 할 때 클로저가 실제로 다른 구조체보다 눈에 띄는 곳 은이 JavaScript 예제와 같이 클로저wikipedia 페이지에서 한 줄로 만들 수 있습니다 .

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );
}

여기에서 threshold정의 된 위치에서 사용되는 위치까지 간결하고 자연스럽게 전달됩니다. 범위는 가능한 한 작게 제한됩니다. filter임계 값과 같은 클라이언트 정의 데이터를 전달할 수 있도록 작성하지 않아도됩니다. 이 작은 함수에서 임계 값을 전달하기위한 목적으로 만 중간 구조를 정의 할 필요는 없습니다. 완전히 독립적입니다.

당신은 할 수 폐쇄하지 않고 쓰기,하지만 더 많은 코드를 필요로하고, 따라하기 어렵게 될 것이다. 또한 JavaScript에는 상당히 장황한 람다 구문이 있습니다. 예를 들어 스칼라에서 전체 함수 본문은 다음과 같습니다.

bookList filter (_.sales >= threshold)

그러나 ECMAScript 6을 사용할 수 있다면 , 팻 화살표 기능 덕분에 JavaScript 코드조차 훨씬 더 단순 해지고 실제로 한 줄에 넣을 수 있습니다.

const bestSellingBooks = (threshold) => bookList.filter(book => book.sales >= threshold);

자신의 코드에서 한 장소에서 다른 장소로 임시 값을 전달하기 위해 많은 상용구를 생성하는 장소를 찾으십시오. 이들은 클로저로 교체를 고려할 수있는 훌륭한 기회입니다.


답변

설명을 위해이 훌륭한 블로그 게시물에서 클로저에 대한 코드를 빌릴 것 입니다. JavaScript이지만 클로저는 JavaScript에서 매우 중요하기 때문에 클로저가 클로저에 대해 사용하는 대부분의 블로그 게시물입니다.

배열을 HTML 테이블로 렌더링하려고한다고 가정하겠습니다. 다음과 같이 할 수 있습니다.

function renderArrayAsHtmlTable (array) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + object + "</td></tr>";
  }
  table += "</table>";
  return table;
}

그러나 배열의 각 요소가 렌더링되는 방법에 대해서는 JavaScript의 도움을받습니다. 렌더링을 제어하려면 다음을 수행하십시오.

function renderArrayAsHtmlTable (array, renderer) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + renderer(object) + "</td></tr>";
  }
  table += "</table>";
  return table;
}

이제 원하는 렌더링을 반환하는 함수를 전달할 수 있습니다.

각 테이블 행에 누계를 표시하려면 어떻게합니까? 총계를 추적하는 변수가 필요합니까? 클로저를 사용하면 누적 합계 변수를 닫는 렌더러 함수를 작성할 수 있으며 누적 합계를 추적 할 수있는 렌더러를 작성할 수 있습니다.

function intTableWithTotals (intArray) {
  var total = 0;
  var renderInt = function (i) {
    total += i;
    return "Int: " + i + ", running total: " + total;
  };
  return renderObjectsInTable(intArray, renderInt);
}

여기서 일어나는 마술 은 반복적으로 호출되고 종료 되더라도 변수에 대한 액세스를 유지 한다는 것 입니다.renderInt totalrenderInt

JavaScript보다 전통적인 객체 지향 언어에서이 총 변수를 포함하는 클래스를 작성하고 클로저를 작성하는 대신 전달할 수 있습니다. 그러나 폐쇄는 훨씬 강력하고 깨끗하며 우아한 방식입니다.

추가 자료


답변

목적 closures은 단순히 상태 를 보존하는 것 입니다. 따라서 이름 closure– 상태 가 닫힙니다 . 자세한 설명을 쉽게하기 위해 Javascript를 사용하겠습니다.

일반적으로 기능이 있습니다

function sayHello(){
    var txt="Hello";
    return txt;
}

여기서 변수의 범위는이 함수에 바인딩됩니다. 따라서 실행 후 변수 txt가 범위를 벗어납니다. 함수 실행이 완료된 후에는 액세스하거나 사용할 수 없습니다.

클로저는 언어 구성으로, 앞서 말했듯이 변수의 상태를 보존하고 범위를 연장 할 수 있습니다.

다른 경우에 유용 할 수 있습니다. 하나의 유스 케이스는 고차 함수 의 구성입니다 .

수학 및 컴퓨터 과학에서 고차 함수 (기능적 형태, 함수 또는 펑터)는 다음 중 하나 이상을 수행하는 함수입니다. 1

  • 하나 이상의 기능을 입력으로 사용
  • 함수를 출력

간단하지만 분명히 너무 유용한 예는 다음과 같습니다.

 makeadder=function(a){
     return function(b){
         return a+b;
     }
 }

 add5=makeadder(5);
 console.log(add5(10)); 

당신은 함수 정의 makedadder입력으로 하나 개의 매개 변수를 사용하고 반환, 기능 . 이 외부 기능 function(a){}내부 function(b){}{} 사용자가 정의 허점 (묵시적으로) 다른 함수 add5높은 순서 연료 소모량을 호출 한 결과로는 makeadder. makeadder(5)익명 ( inner ) 함수를 반환합니다.이 함수는 1 개의 매개 변수를 사용하고 외부 함수의 매개 변수와 내부 함수 의 매개 변수의 합을 반환합니다 .

트릭 복귀하면서 있다는 것이다 내부 첨가 실제 않는 기능, 외부 함수의 매개 변수의 범위는 ( a) 보존된다. 매개 변수 는 add5 기억합니다 .a5

또는 하나 이상의 유용한 예를 보여주기 위해 :

  makeTag=function(openTag, closeTag){
     return function(content){
         return openTag +content +closeTag;
     }
 }

 table=makeTag("<table>","</table>")
 tr=makeTag("<tr>", "</tr>");
 td=makeTag("<td>","</td>");
 console.log(table(tr(td("I am a Row"))));

또 다른 일반적인 사용 사례는 소위 IIFE = 즉시 호출 된 함수 표현식입니다. 자바 스크립트에서 개인 멤버 변수 를 가짜 로 만드는 것은 매우 일반적입니다 . 이것은 정의가 호출 된 직후에 private scope = 생성하는 함수를 통해 수행 closure됩니다. 구조는 function(){}()입니다. ()정의 후 괄호 를 확인하십시오. 이를 통해 모듈 패턴드러내는 객체 생성에 사용할 수 있습니다. 트릭은 범위를 만들고 IIFE를 실행 한 후이 범위에 액세스 할 수있는 개체를 반환하는 것입니다.

Addi의 예는 다음과 같습니다.

 var myRevealingModule = (function () {

         var privateVar = "Ben Cherry",
             publicVar = "Hey there!";

         function privateFunction() {
             console.log( "Name:" + privateVar );
         }

         function publicSetName( strName ) {
             privateVar = strName;
         }

         function publicGetName() {
             privateFunction();
         }


         // Reveal public pointers to
         // private functions and properties

         return {
             setName: publicSetName,
             greeting: publicVar,
             getName: publicGetName
         };

     })();

 myRevealingModule.setName( "Paul Kinlan" );

반환 된 객체에는 함수 (예 :)에 대한 참조가 있으며 publicSetName, “private”변수에 액세스 할 수 있습니다 privateVar.

그러나 이것들은 Javascript의 더 특별한 사용 사례입니다.

클로저가 가장 잘 수행 할 수있는 프로그래머가 수행해야 할 특정 작업은 무엇입니까?

몇 가지 이유가 있습니다. 그가 기능적 패러다임 을 따르기 때문에 자연 스러울 수도 있습니다 . 또는 Javascript에서 : 언어의 단점을 피하기 위해 클로저에 의존 하는 것은 단지 필요한 것입니다 .


답변

클로저에는 두 가지 주요 사용 사례가 있습니다.

  1. 비동기. 시간이 걸리는 작업을 수행하고 완료되면 무언가를 수행한다고 가정 해 봅시다. 코드가 완료되기를 기다리게하여 추가 실행을 차단하고 프로그램이 응답하지 않게 할 수 있으며 작업을 비동기 적으로 호출하여 “백그라운드에서이 긴 작업을 시작하고 완료되면이 클로저를 실행합니다”라고 말하고, 클로저는 완료되면 실행할 코드를 포함합니다.

  2. 콜백. 언어 및 플랫폼에 따라 “대리인”또는 “이벤트 핸들러”라고도합니다. 아이디어는 잘 정의 된 특정 지점에서 이벤트 를 실행하여이를 설정하는 코드에 의해 전달 된 클로저를 실행하는 사용자 정의 가능한 객체를 가지고 있다는 것입니다. 예를 들어, 프로그램의 UI에 버튼이있을 수 있으며, 사용자가 버튼을 클릭 할 때 실행될 코드를 포함하는 클로저를 제공합니다.

클로저에는 몇 가지 다른 용도가 있지만 두 가지 주요 용도가 있습니다.


답변

다른 몇 가지 예 :

정렬
대부분의 정렬 기능은 객체 쌍을 비교하여 작동합니다. 일부 비교 기술이 필요합니다. 특정 연산자로 비교를 제한하는 것은 다소 융통성이없는 정렬을 의미합니다. 훨씬 더 좋은 방법은 정렬 함수에 대한 인수로 비교 함수를받는 것입니다. 경우에 따라 상태 비 저장 비교 기능 (예 : 숫자 또는 이름 목록 정렬)이 제대로 작동하지만 비교에 상태가 필요한 경우 어떻게해야합니까?

예를 들어, 특정 위치까지의 거리별로 도시 목록을 정렬 해보십시오. 추악한 해결책은 해당 위치의 좌표를 전역 변수에 저장하는 것입니다. 이것은 비교 함수 자체를 무 상태로 만들지 만 전역 변수를 희생합니다.

이 방법을 사용하면 여러 스레드가 동시에 동일한 도시 목록을 서로 다른 두 위치까지의 거리별로 정렬 할 수 없습니다. 위치를 둘러싸는 클로저는이 문제를 해결하고 전역 변수의 필요성을 제거합니다.

난수
원본 rand()은 인수를 취하지 않았습니다. 의사 난수 생성기는 상태가 필요합니다. 일부 (예 : Mersenne Twister)에는 많은 상태가 필요합니다. 단순하지만 끔찍한 rand()필요한 상태 조차도 . 새로운 난수 생성기에서 수학 일지를 읽으면 불가피하게 전역 변수가 표시됩니다. 그것은 기술 개발자에게 좋으며 호출자에게는 좋지 않습니다. 구조에서 해당 상태를 캡슐화하고 구조를 난수 생성기로 전달하는 것은 전역 데이터 문제를 해결하는 한 가지 방법입니다. 이것은 많은 비 OO 언어에서 난수 생성기 재진입을 위해 사용되는 방법입니다. 클로저는 해당 상태를 호출자로부터 숨 깁니다. 클로저는 간단한 호출 순서 rand()와 캡슐화 된 상태의 재진입을 제공합니다 .

PRNG보다 난수가 더 많습니다. 임의성을 원하는 대부분의 사람들은 특정 방식으로 배포하기를 원합니다. 나는 0에서 1 사이에서 무작위로 그린 숫자로 시작하거나 짧게 U (0,1)로 시작합니다. 0에서 최대 값 사이의 정수를 생성하는 모든 PRNG가 수행합니다. 임의의 정수를 최대로 (부동 소수점으로) 간단히 나누십시오. 이를 구현하는 편리하고 일반적인 방법은 클로저 (PRNG)와 최대 값을 입력으로하는 클로저를 만드는 것입니다. 이제 U (0,1)에 대해 일반적이고 사용하기 쉬운 랜덤 생성기가 있습니다.

U (0,1) 외에 다른 많은 분포가 있습니다. 예를 들어, 특정 평균 및 표준 편차가있는 정규 분포입니다. 내가 실행 한 모든 정규 분포 생성기 알고리즘은 U (0,1) 생성기를 사용합니다. 정규 생성기를 생성하는 편리하고 일반적인 방법은 U (0,1) 생성기, 평균 및 표준 편차를 상태로 캡슐화하는 클로저를 만드는 것입니다. 이것은 적어도 개념적으로 클로저를 인수로 취하는 클로저를 취하는 클로저입니다.


답변

클로저는 run () 메소드를 구현하는 객체와 동일하며, 반대로 클로저로 객체를 에뮬레이션 할 수 있습니다.

  • 클로저의 장점은 고차 함수, 간단한 콜백 (또는 전략 패턴)과 같이 함수를 기대하는 모든 위치에서 쉽게 사용할 수 있다는 것입니다. 임시 폐쇄를 구축하기 위해 인터페이스 / 클래스를 정의 할 필요가 없습니다.

  • 객체의 장점은 더 복잡한 상호 작용을 가질 수 있다는 것입니다 : 여러 방법 및 / 또는 다른 인터페이스.

따라서 클로저 또는 객체를 사용하는 것은 대부분 스타일의 문제입니다. 다음은 클로저가 쉽지만 객체로 구현하기가 불편한 것의 예입니다.

 (let ((seen))
    (defun register-name (name)
       (pushnew name seen :test #'string=))

    (defun all-names ()
       (copy-seq seen))

    (defun reset-name-registry ()
       (setf seen nil)))

기본적으로 전역 클로저를 통해서만 액세스되는 숨겨진 상태를 캡슐화합니다. 객체를 참조 할 필요는 없으며 세 가지 함수로 정의 된 프로토콜 만 사용하십시오.


나는 일부 언어에서는 객체의 수명을 정확하게 제어하는 ​​것이 가능하지만 클로저에 대해서도 동일한 것은 아니라는 사실에 대한 supercat의 첫 번째 의견을 신뢰합니다. 그러나 가비지 수집 언어의 경우 객체의 수명은 일반적으로 제한이 없으므로 호출되지 않아야하는 동적 컨텍스트에서 호출 될 수있는 클로저를 작성할 수 있습니다 (스트림 후 클로저에서 읽음) 예를 들어 닫힙니다.

그러나 클로저 실행을 보호하는 제어 변수를 캡처하여 이러한 오용을 방지하는 것은 매우 간단합니다. 더 정확하게 말하면, 여기에 내가 생각한 것이 있습니다 (Common Lisp에서).

(defun guarded (function)
  (let ((active t))
    (values (lambda (&rest args)
              (when active
                (apply function args)))
            (lambda ()
              (setf active nil)))))

여기서 함수 지정자를 가져 와서 function두 개의 클로저를 반환합니다. 두 클로저는 모두 다음과 같은 로컬 변수를 캡처합니다 active.

  • 첫 번째 는 true function일 때만 위임 active합니다.
  • 두 번째는 aka로 설정 action됩니다 .nilfalse

대신에 클로저가 호출되지 않아야 할 때 호출되는 경우 예외를 발생시킬 수 (when active ...)있는 (assert active)표현식 을 가질 수 있습니다. 또한 안전하지 않은 코드 잘못 사용하면 이미 예외throw 할 수 있으므로 이러한 래퍼가 거의 필요하지 않습니다.

사용 방법은 다음과 같습니다.

(use-package :metabang-bind) ;; for bind

(defun example (obj1 obj2)
  (bind (((:values f f-deactivator)(guarded (lambda () (do-stuff obj1))))
         ((:values g g-deactivator)(guarded (lambda () (do-thing obj2)))))

    ;; ensure the closure are inactive when we exit
    (unwind-protect
         ;; pass closures to other functions
         (progn
           (do-work f)
           (do-work g))

      ;; cleanup code: deactivate closures
      (funcall f-deactivator)
      (funcall g-deactivator))))

비활성화 클로저는 다른 기능에도 적용될 수 있습니다. 여기, 지역 active변수는 공유되지 않습니다 fg; 또한, 추가에 active, f단지를 의미 obj1하고 g단지를 말한다 obj2.

supercat이 언급 한 또 다른 요점은 클로저로 인해 메모리 누수가 발생할 수 있지만 불행히도 가비지 수집 환경의 거의 모든 경우에 해당됩니다. 사용 가능한 경우, 약한 포인터 로 해결할 수 있습니다 (클로저 자체는 메모리에 보관 될 수 있지만 다른 자원의 가비지 콜렉션은 막지 않습니다).


답변

아직 언급되지 않은 것은 아니지만 더 간단한 예일 수 있습니다.

시간 초과를 사용하는 JavaScript 예제는 다음과 같습니다.

// Example function that logs something to the browser's console after a given delay
function delayedLog(message, delay) {
  // this function will be called when the timer runs out
  var fire = function () {
    console.log(message); // closure magic!
  };

  // set a timeout that'll call fire() after a delay
  setTimeout(fire, delay);
}

여기서 발생하는 delayedLog()것은 호출되면 시간 초과를 설정 한 직후 반환되며 백그라운드에서 시간 초과가 계속 표시됩니다.

그러나 시간 초과가 만료되어 fire()함수를 호출하면 콘솔 message은 원래 전달 된 것을 표시합니다 . 폐쇄 delayedLog()fire()통해 여전히 사용할 수 있기 때문입니다 . delayedLog()매번 다른 메시지와 지연으로 원하는만큼 전화 를 걸 수 있으며 올바른 일을 할 것입니다.

그러나 JavaScript에는 클로저가 없다고 가정 해 봅시다.

한 가지 방법은 setTimeout()“잠자기”기능과 같은 차단 을 만드는 것이므로 delayedLog()시간 초과가 끝날 때까지 범위가 사라지지 않습니다. 그러나 모든 것을 차단하는 것은 그리 좋지 않습니다.

또 다른 방법은 의 범위가 사라진 message후에 액세스 할 수있는 다른 범위에 변수 를 넣는 것 delayedLog()입니다.

전역 또는 적어도 “광범위한”변수를 사용할 수 있지만 어떤 메시지가 어떤 시간 초과와 함께 진행되는지 추적하는 방법을 알아 내야합니다. 그러나 원하는 지연을 설정할 수 있기 때문에 순차적 인 FIFO 대기열 일 수는 없습니다. 따라서 “선입 선출”또는 “선입 선출”일 수 있습니다 따라서 시간이 지정된 함수를 필요한 변수에 연결하는 다른 방법이 필요합니다.

메시지와 함께 타이머를 “그룹화”하는 시간 초과 객체를 인스턴스화 할 수 있습니다. 개체의 컨텍스트는 어느 정도 범위 내에서 유지됩니다. 그런 다음 객체의 컨텍스트에서 타이머를 실행하면 올바른 메시지에 액세스 할 수 있습니다. 그러나 참조가 없으면 가비지 수집 (폐쇄없이 암시 적 참조가 없기 때문에) 때문에 해당 객체를 저장해야합니다. 시간 초과가 발생하면 객체를 제거해야합니다. 그렇지 않으면 그냥 붙어 있습니다. 따라서 일종의 시간 초과 객체 목록이 필요하며 주기적으로 제거 할 “사용 된”객체가 있는지 확인하십시오. 그렇지 않으면 객체가 목록에서 추가 및 제거됩니다.

그래서 … 그래, 이건 지루 해져

고맙게도 특정 변수를 유지하기 위해 더 넓은 범위 또는 얽힌 객체를 사용할 필요는 없습니다. JavaScript에는 클로저가 있기 때문에 이미 필요한 범위가 있습니다. message필요할 때 변수에 액세스 할 수있는 범위입니다 . 그리고 그 때문에 delayedLog()위와 같이 글을 쓰지 않아도됩니다.