프로그램이 클로저를 사용하는 이유는 무엇입니까?


58

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

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

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


7
"최근에 폐쇄 설명을 검토했습니다"-링크가 누락 되었습니까?
Dan Pichelman 2016 년

2
비동기 작업 에 대한 설명을 관련 답변 아래의 주석 에 넣어야합니다 . 원래 질문의 일부가 아니며 답변자에게 편집 내용을 알리지 않습니다.
Robert Harvey

5
깔끔함 : 클로저의 결정적인 포인트는 어휘 범위 (동적 범위와 반대)이며, 어휘 범위가없는 클로저는 쓸모가 없습니다. 폐쇄는 때때로 Lexical Closures라고합니다.
Hoffmann

1
클로저를 사용하면 기능인스턴스화 할 수 있습니다 .
user253751 2016

1
함수형 프로그래밍에 관한 좋은 책을 읽으십시오. 아마도 SICP로
Basile Starynkevitch

답변:


33

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

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

고차 함수를 사용할 때, 실제로 상태를 전달 해야 할 때 클로저가 실제로 다른 구조체보다 눈에 띄는 곳 은이 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);

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


클로저가 커플 링을 정확히 어떻게 줄입니까?
sbichenko 2016 년

클로저없이 데이터를 전달 하려면 bestSellingBooks코드와 filter코드 (예 : 특정 인터페이스 또는 사용자 데이터 인수)에 제한을 두어야 threshold합니다. 이는 두 기능을 훨씬 덜 재사용 가능한 방식으로 연결합니다.
Karl Bielefeldt

51

설명을 위해이 훌륭한 블로그 게시물에서 클로저에 대한 코드를 빌릴 것 입니다. 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보다 전통적인 객체 지향 언어에서이 총 변수를 포함하는 클래스를 작성하고 클로저를 작성하는 대신 전달할 수 있습니다. 그러나 폐쇄는 훨씬 강력하고 깨끗하며 우아한 방식입니다.

추가 자료


10
일반적으로 "퍼스트 클래스 함수 == 하나의 메서드 만있는 개체", "클로저 == 하나의 메서드와 상태 만있는 개체", "object == 클로저 번들"이라고 말할 수 있습니다.
Jörg W Mittag 2016 년

좋아 보인다 또 다른 것은, 이것은 학자 인 수 있습니다, 자바 스크립트가 있다는 것입니다 이다 객체 지향, 당신은 할 수 전체 변수를 포함하는 객체를 생성하고 그 주위에 전달합니다. 클로저는 훨씬 강력하고 깨끗하며 우아하게 유지되며 훨씬 더 관용적 인 자바 스크립트를 만들지 만, 작성된 방식은 자바 스크립트가 객체 지향 방식으로이를 수행 할 수 없음을 의미 할 수 있습니다.
KRyan

2
실제로, 정말 놀랍게도 : 클로저는 JavaScript를 객체 지향적으로 만듭니다! OO는 데이터 추상화에 관한 것이며 클로저는 JavaScript에서 데이터 추상화를 수행하는 방법입니다.
Jörg W Mittag

@ JörgWMittag : 클로저를 하나의 메소드로만 객체로 볼 수 있듯이 객체를 동일한 변수를 통해 닫는 클로저 컬렉션으로 볼 수 있습니다. 객체의 멤버 변수는 객체 생성자의 로컬 변수 일뿐입니다. 생성자의 범위 내에서 정의되고 나중에 호출 할 수있는 클로저입니다. 생성자 함수는 호출에 사용 된 메소드 이름에 따라 각 클로저에서 전달할 수있는 상위 함수 (오브젝트)를 리턴합니다.
Giorgio

@Giorgio : 실제로, 예를 들어 Scheme에서 객체가 일반적으로 구현되는 방식이며, 실제로 JavaScript로 객체가 구현되는 방식이기도합니다 (Scheme와의 밀접한 관계를 고려할 때 놀라운 일은 아니지만 결국 Brendan Eich는 원래 체계는 Netscape Navigator 내부에 내장 된 체계 해석기를 구현하고 구현했으며, 나중에 "C ++처럼 보이는 객체로 언어를 만들도록"명령을 내린 후 마케팅 요구 사항을 준수하기 위해 최소한의 변경 만 수행했습니다.
Jörg W Mittag 2016 년

22

목적 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에서 : 언어의 단점을 피하기 위해 클로저에 의존 하는 것은 단지 필요한 것입니다 .


"폐쇄의 목적은 단순히 상태를 보존하는 것이므로 이름 폐쇄는 상태를 닫습니다.": 언어에 따라 다릅니다. 클로저는 외부 이름 / 변수를 닫습니다. 이들은 상태 (변경 될 수있는 메모리 위치)를 나타낼 수 있지만 값 (불변)을 나타낼 수도 있습니다. Haskell의 클로저는 상태를 유지하지 않습니다. 클로저가 생성 된 시점과 컨텍스트에서 알려진 정보를 유지합니다.
Giorgio

1
그들이 지금과 폐쇄가가 변경 여부 될 수있는 독립적 여부«을 만든 컨텍스트에 알려진 정보를 보존», 그것은이다 상태 아마도 - 불변 상태 .
토마스 정크

16

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

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

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

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


23
첫 번째 예제도 콜백이기 때문에 기본적으로 콜백입니다.
Robert Harvey

2
@RobertHarvey : 기술적으로는 사실이지만 다른 정신 모델입니다. 예를 들어 (일반적으로 말하면) 이벤트 핸들러는 여러 번 호출되지만 비동기 연속은 한 번만 호출됩니다. 그러나 예, 기술적으로 클로저로 할 일은 콜백입니다. (식 트리로 변환하지 않는 한 완전히 다른 문제입니다.);)
Mason Wheeler

4
@RobertHarvey : 콜백으로 클로저를 보는 것은 실제로이를 효과적으로 사용하지 못하게하는 마음의 틀에 들어가게합니다. 폐쇄는 스테로이드에 대한 콜백입니다.
gnasher729

13
@MasonWheeler 이렇게하면 잘못 들립니다. 콜백은 클로저와 관련이 없습니다. 물론 클로저 내에서 함수를 콜백하거나 콜백으로 클로저를 호출 할 수 있습니다. 그러나 콜백은 클로저가 필요하지 않습니다. 콜백은 단순한 함수 호출입니다. 이벤트 핸들러는 그 자체로 폐쇄되지 않습니다. 델리게이트는 클로저가 필요하지 않습니다. 주로 C # 함수 포인터입니다. 물론 클로저를 구성하는 데 사용할 수 있습니다. 설명이 정확하지 않습니다. 요점은 상태를 닫고 그것을 사용하고 있습니다.
토마스 정크

5
어쩌면 오해를 일으키는 JS 관련 의미가있을 수 있지만이 대답은 나에게 절대적으로 잘못 들립니다. 비동기 또는 콜백은 '호출 가능'한 것을 사용할 수 있습니다. 둘 중 어느 것도 클로저가 필요하지 않습니다. 일반 기능이 잘 작동합니다. 한편, 함수형 프로그래밍에서 정의되거나 파이썬에서 사용되는 클로저는 일부 상태, 즉 변수를 '닫고'내부 변수에서 해당 변수에 대한 일관된 액세스를 제공하기 때문에 Thomas가 말한 것처럼 유용합니다. 함수가 호출되어 여러 번 종료 되더라도
Jonathan Hartley

13

다른 몇 가지 예 :

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

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

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


난수
원본 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) 생성기, 평균 및 표준 편차를 상태로 캡슐화하는 클로저를 만드는 것입니다. 이것은 적어도 개념적으로 클로저를 인수로 취하는 클로저를 취하는 클로저입니다.


7

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


1
일반적으로 구현 되는 클로저의 단점은 메서드의 로컬 변수 중 하나를 사용하는 클로저가 외부 세계에 노출되면 클로저를 정의하는 메서드가 해당 변수를 읽고 쓸 때 제어권을 잃을 수 있다는 것입니다. 대부분의 언어에서, 임시 클로저를 정의하고, 메소드에 전달하고, 메소드를 리턴 한 메소드가 리턴되면 존재하지 않을 것을 보장하는 편리한 방법은 없습니다. 예기치 않은 스레딩 컨텍스트에서 클로저가 실행되지 않도록합니다.
supercat

@ supercat : Objective-C와 Swift를 살펴보십시오.
gnasher729 2016 년

1
@supercat 상태 저장 객체와 동일한 단점이 존재하지 않습니까?
Andres F.

@AndresF .: 무효화를 지원하는 래퍼에 상태 저장 객체를 캡슐화 할 수 있습니다. 코드 List<T>가 (가설 적 클래스)에 개인용으로 캡슐화 TemporaryMutableListWrapper<T>되어 외부 코드에 노출되는 경우 래퍼를 무효화하면 외부 코드가 더 이상을 조작하는 방법을 갖지 않을 수 있습니다 List<T>. 예상 한 목적에 부합하면 무효화를 허용하도록 클로저를 디자인 할 수 있지만 거의 편리하지 않습니다. 폐쇄는 특정 패턴을 편리하게 만들기 위해 존재하며이를 보호하기위한 노력은이를 무효화합니다.
supercat

1
@coredump : C #에서 두 클로저에 공통 변수가있는 경우 한 컴파일러에서 공유 변수에 대한 변경 사항을 다른 클로저에서 볼 수 있어야하므로 동일한 컴파일러 생성 객체가 두 가지를 모두 처리합니다. 이러한 공유를 피하려면 각 클로저가 고유 한 비공유 변수를 보유하는 고유 한 객체 일뿐만 아니라 공유 변수를 보유하는 공유 객체에 대한 참조가 필요했습니다. 불가능하지는 않지만 대부분의 가변 액세스에 추가적인 수준의 역 참조를 추가하여 모든 것을 느리게 만들었습니다.
supercat

6

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

시간 초과를 사용하는 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()위와 같이 글을 쓰지 않아도됩니다.


폐쇄 라고 부르는 데 문제가 있습니다 . 어쩌면 나는 우연히 그것을 폐쇄 동전으로 만들 것 입니다. message의 기능 범위에 포함 fire되므로 추가 호출에서 참조됩니다. 하지만 실수로 그렇게합니다. 기술적으로는 폐쇄입니다. 어쨌든 +1;)
Thomas Junk

@ThomasJunk 잘 따라 갈지 잘 모르겠습니다. " 사고 폐쇄"는 어떻게 보입니까? 나는 makeadder위 의 예를 보았습니다 . 두 개가 아닌 단일 인수를 취하는 "curried"함수를 반환합니다. 같은 수단을 사용하여 인수가 0 인 함수를 만듭니다. 나는 그것을 반환하지 않고 setTimeout대신에 전달합니다 .
Flambino

»아무것도 돌려주지 않습니다«아마 그 점이 저에게 차이를 가져옵니다. 나는 내 "관심"을 분명히 표현할 수 없다;) 기술적 인면에서 당신은 100 % 옳습니다. 참조 하면 클로저 messagefire생성됩니다. 그리고 호출 될 때 setTimeout보존 상태를 사용합니다.
Thomas 정크

1
@ThomasJunk 약간 다른 냄새가 나는 방법을 봅니다. ), 나는 그것을 일부러 그런 식으로 코딩 확신 - 나는 어떤 속도로, 나는 "실수"를 호출 할 것입니다,하지만 당신의 관심사를 공유 : 또는하지 않을 수 있습니다
Flambino

3

PHP 는 다른 언어로 실제 예제를 보여주기 위해 사용될 수 있습니다.

protected function registerRoutes($dic)
{
  $router = $dic['router'];

  $router->map(['GET','OPTIONS'],'/api/users',function($request,$response) use ($dic)
  {
    $controller = $dic['user_api_controller'];
    return $controller->findAllAction($request,$response);
  })->setName('api_users');
}

그래서 기본적으로 / api / users URI에 대해 실행될 함수를 등록하고 있습니다 . 이것은 실제로 미들웨어 기능으로 스택에 저장됩니다. 다른 기능은 그 주위에 포장됩니다. Node.js / Express.js 와 거의 비슷 합니다.

의존성 주입 이 호출 될 때 용기의 내부 기능 (이용 절 통해) 가능하다. 일종의 라우트 액션 클래스를 만들 수는 있지만이 코드는 더 간단하고 빠르며 유지 관리하기 쉽습니다.


-1

폐쇄 일류 데이터로서 취급 될 수있는 변수를 포함한 임의의 코드의 한 부분이다.

간단한 예가 좋은 예입니다. qsort : 데이터를 정렬하는 함수입니다. 두 객체를 비교하는 함수에 대한 포인터를 제공해야합니다. 따라서 함수를 작성해야합니다. 이 함수는 매개 변수화되어야 할 수도 있습니다. 이는 정적 변수를 제공한다는 의미입니다. 스레드 안전하지 않다는 의미입니다. 당신은 DS에 있습니다. 따라서 함수 포인터 대신 클로저를 사용하는 대안을 작성하십시오. 매개 변수가 클로저의 일부가되기 때문에 매개 변수화 문제를 즉시 해결합니다. 정렬 함수를 호출하는 코드와 객체를 직접 비교하는 방식을 작성하기 때문에 코드를 더 읽기 쉽게 만듭니다 .

많은 양의 보일러 플레이트 코드와 조정이 필요한 작지만 필수적인 코드가 필요한 조치가 필요한 상황이 많이 있습니다. 클로저 매개 변수를 사용하고 그 주위의 모든 상용구 코드를 수행 하는 함수를 한 번 작성하면 상용구 코드를 피할 수 있습니다. 그런 다음이 함수를 호출하고 코드를 전달하여 폐쇄로 적용 할 수 있습니다. 코드를 작성하는 매우 간결하고 읽기 쉬운 방법.

사소하지 않은 코드를 여러 가지 상황에서 수행해야하는 기능이 있습니다. 이것은 코드 중복 또는 왜곡 된 코드를 생성하여 사소하지 않은 코드가 한 번만 표시되도록하는 데 사용되었습니다. 사소한 : 변수에 클로저를 할당하고 필요할 때마다 가장 분명한 방식으로 호출합니다.

멀티 스레딩 : iOS / MacOS X에는 "배경 스레드에서이 클로저 수행", "... 메인 스레드에서", "... 메인 스레드에서 10 초 후"와 같은 기능을 수행하는 기능이 있습니다. 멀티 스레딩을 간단 하게 만듭니다 .

비동기식 호출 : OP가 본 것입니다. 인터넷에 액세스하거나 GPS 좌표를 읽는 것과 같이 시간이 걸릴 수있는 모든 통화는 결과를 기다릴 수없는 것입니다. 따라서 백그라운드에서 작업을 수행하는 함수가 있고 완료되면 수행 할 작업을 알려주는 클로저를 전달합니다.

작은 시작입니다. 컴팩트하고 읽기 쉽고 신뢰할 수 있고 효율적인 코드 생성 측면에서 클로저가 혁신적인 5 가지 상황.


-4

클로저는 사용될 메소드를 작성하는 간단한 방법입니다. 별도의 메소드를 선언하고 작성하는 노력을 덜어줍니다. 메소드가 한 번만 사용되며 메소드 정의가 짧은 경우에 유용합니다. 함수 이름, 리턴 유형 또는 액세스 수정자를 지정할 필요가 없으므로 입력이 줄어 듭니다. 또한 코드를 읽을 때 메소드 정의를 다른 곳에서 찾을 필요가 없습니다.

위는 Dan Avidar의 Lambda Expressions 이해를 요약 한 것입니다.

이것은 대안 (폐쇄 대 방법)과 각각의 이점을 명확히하기 때문에 클로저 사용을 분명히했습니다.

다음 코드는 설정 중에 한 번만 사용됩니다. viewDidLoad 아래에 적어두면 다른 곳에서 검색하지 않아도되며 코드 크기가 줄어 듭니다.

myPhoton!.getVariable("Temp", completion: { (result:AnyObject!, error:NSError!) -> Void in
  if let e = error {
    self.getTempLabel.text = "Failed reading temp"
  } else {
    if let res = result as? Float {
    self.getTempLabel.text = "Temperature is \(res) degrees"
    }
  }
})

또한 프로그램의 다른 부분을 차단하지 않고 비동기 프로세스를 완료 할 수 있으며 클로저는 후속 함수 호출에서 재사용 할 수있는 값을 유지합니다.

또 다른 폐쇄; 이것은 가치를 포착합니다 ...

let animals = ["fish", "cat", "chicken", "dog"]
let sortedStrings = animals.sorted({ (one: String, two: String) -> Bool in return one > two
}) println(sortedStrings)

4
불행히도 이것은 클로저와 람다를 혼동합니다. 람다는 클로저를 사용하는 경우가 많습니다. a) 변수가 포함 된 특정 메소드 컨텍스트 내에서 그리고 그에 따라 정의 된 경우 일반적으로 훨씬 유용합니다. 그러나 실제 클로저는 기본적으로 일류 함수를 정의하고 나중에 사용할 수 있도록 전달하는 기능인 람다 (lambda)와는 아무런 관련이 없습니다.
Nathan Tuggy 2016 년

이 답변에 투표하는 동안이 내용을 읽으십시오. 언제 투표해야합니까? 엄청나게 조잡하고 노력이 많이 들지 않는 게시물이나 명확하고 위험 할 수있는 답변이있을 때마다 다운 보트를 사용하십시오. 내 대답에는 클로저를 사용하는 몇 가지 이유가 없을 수 있지만 엄청나게 거칠거나 위험하지 않습니다. 함수형 프로그래밍과 람다 표기법에 중점을 둔 클로저에 대한 많은 논문과 토론이 있습니다. 제 대답은 함수형 프로그래밍에 대한이 설명이 클로저를 이해하는 데 도움이되었다는 것입니다. 그와 지속적인 가치.
Bendrix

1
대답은 아마 위험 하지는 않지만 "분명히 […] 잘못되었습니다". 그것은 상보성이 높고 따라서 종종 함께 사용되는 개념에 대해 잘못된 용어를 사용합니다. 주소를 지정하지 않은 채로 읽는 경우 프로그래머가 설계 문제를 일으킬 수 있습니다. (그리고 내가 알 수있는 한, 코드 예제에는 클로저가없고 람다 함수 만 포함되어있어 클로저가 도움이되는 이유를 설명하는 데 도움이되지 않습니다.)
Nathan Tuggy

Nathan, 그 코드 예제는 Swift의 클로저입니다. 당신이 나를 의심하는지 다른 사람에게 물어볼 수 있습니다. 따라서 그것이 폐쇄 인 경우 투표를 하시겠습니까?
Bendrix

1
애플 은 문서 에서 클로저가 의미하는 바를 정확하게 설명 하고있다 . "클로저는 코드에 전달되어 사용할 수있는 자체 포함 된 기능 블록입니다. Swift의 클로저는 C 및 Objective-C의 블록과 유사하며 다른 프로그래밍 언어의 람다와 유사합니다." 구현과 용어에 익숙해지면 혼란 스러울 수 있습니다 .
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.