[function] ‘클로저’와 ‘람다’의 차이점은 무엇입니까?

누군가 설명해 주시겠습니까? 나는 기본 개념을 이해하지만 종종 상호 교환 적으로 사용되는 것을 보았고 혼란스러워합니다.

그리고 우리가 여기 왔으니, 그것들은 일반 기능과 어떻게 다릅니 까?



답변

람다 단지 익명 함수입니다 – 아니 이름으로 정의 된 함수. 구성표와 같은 일부 언어에서는 명명 된 함수와 동일합니다. 실제로 함수 정의는 람다를 변수에 내부적으로 바인딩하여 다시 작성됩니다. 파이썬과 같은 다른 언어에서는 서로간에 (필요하지 않은) 구별이 있지만 그렇지 않으면 같은 방식으로 동작합니다.

폐쇄 어떤 함수 를 통해 폐쇄 환경 이 정의시킨. 이는 매개 변수 목록에없는 변수에 액세스 할 수 있음을 의미합니다. 예 :

def func(): return h
def anotherfunc(h):
   return func()

때문에, 오류가 발생합니다 func하지 않는 이상 주변 에서 환경 anotherfunch정의되지 않는다. func지구 환경을 폐쇄합니다. 이것은 작동합니다 :

def anotherfunc(h):
    def func(): return h
    return func()

여기에 func가 정의되어 anotherfunc있으며 파이썬 2.3 이상 (또는 이와 비슷한 숫자) 에서 클로저가 거의 올바르게 수정 되면 (돌연변이는 여전히 작동하지 않음), 이는 환경을 닫고 anotherfunc 내부 변수에 액세스 할 수 있음을 의미합니다 그것. 사용할 때 파이썬에서 3.1, 돌연변이도 작동 키워드를 .nonlocal

또 다른 중요한 점 funcanotherfunc더 이상 평가되지 않더라도 환경을 계속 폐쇄하는 것 입니다 anotherfunc. 이 코드는 다음과 같이 작동합니다.

def anotherfunc(h):
    def func(): return h
    return func

print anotherfunc(10)()

10이 인쇄됩니다.

아시다시피 람다 와는 아무런 관련이 없습니다. 두 가지 다른 개념이지만 서로 관련이 있습니다.


답변

이 StackOverflow 질문에 대한 답변에서도 람다와 클로저에 대해 많은 혼란이 있습니다. 특정 프로그래밍 언어 또는 다른 단서가없는 프로그래머와의 연습에서 클로저에 대해 배운 임의의 프로그래머에게 묻는 대신 소스로 이동하십시오 (모두 시작된 곳). 람다와 클로저는 최초의 전자 컴퓨터가 존재하기 전인 30 년대에 알론조 교회가 발명 한 람다 미적분학 (Lamda Calculus) 에서 나왔기 때문에 이것이 제가 이야기 하고있는 출처 입니다.

람다 미적분학은 세계에서 가장 간단한 프로그래밍 언어입니다. 당신이 할 수있는 유일한 것 : ►

  • 응용 프로그램 :로 표현 된 표현을 다른 표현에 적용 f x합니다.
    (a이라고 생각 함수 호출 , f함수이고, x그것의 유일한 파라미터이다)
  • AbstraCTION : 표현식에서 발생하는 심볼을 바인딩하여이 심볼이 “슬롯”, 값으로 채워지기를 기다리는 빈 상자, “변수”를 표시합니다. 그리스어 문자 λ(람다), 기호 이름 (예 x🙂 ., 식 앞에 점을 붙여서 수행합니다. 그러면 표현식을 하나의 매개 변수를 기대 하는 함수 로 변환합니다 . 예를 들어 : 표현식 을 가져 와서이 표현식 의 기호 가 바운드 변수 임을 나타냅니다. 매개 변수로 제공 한 값으로 대체 할 수 있습니다.
    이 방법으로 정의 된 함수는 익명입니다.
    λx.x+2x+2x
    – 이름이 없으므로 아직 참조 할 수 없지만 다음 과 같이 대기중인 매개 변수를 제공하여 즉시 호출 할 수 있습니다 (응용 프로그램 기억?) (λx.x+2) 7. 그런 다음 적용된 람다 의 하위 표현식에서와 7같이 표현식 (이 경우 리터럴 값) 이 대체 되므로 일반적인 산술 규칙에 따라 줄어 듭니다 .xx+27+29

그래서 우리는 수수께끼 중 하나를 해결했습니다.
lambda 는 위 예제 의 익명 함수 입니다 λx.x+2.


다른 프로그래밍 언어에서는 기능 추상화 (lambda) 구문이 다를 수 있습니다. 예를 들어, JavaScript에서는 다음과 같습니다.

function(x) { return x+2; }

다음과 같은 매개 변수에 즉시 적용 할 수 있습니다.

(function(x) { return x+2; })(7)

또는이 익명 함수 (lambda)를 변수에 저장할 수 있습니다.

var f = function(x) { return x+2; }

효과적으로 이름을 부여 f하여 참조하고 여러 번 나중에 호출 할 수 있습니다.

alert(  f(7) + f(10)  );   // should print 21 in the message box

그러나 이름을 지정할 필요는 없습니다. 즉시 전화 할 수 있습니다.

alert(  function(x) { return x+2; } (7)  );  // should print 9 in the message box

LISP에서 람다는 다음과 같이 만들어집니다.

(lambda (x) (+ x 2))

그런 람다를 매개 변수에 즉시 적용하여 호출 할 수 있습니다.

(  (lambda (x) (+ x 2))  7  )


OK, 지금은 다른 신비를 해결하기 위해 시간하십시오 무엇 폐쇄 . 그렇게하기 위해 람다 식의 기호 ( 변수 )에 대해 이야기합시다 .

내가 말했듯이, 람다 추상화는 하위 표현에 기호를 바인딩 하여 대체 가능한 매개 변수가 됩니다. 이러한 기호를 bound 라고 합니다. 그러나 표현에 다른 상징이 있다면 어떨까요? 예를 들면 다음과 같습니다 λx.x/y+2.. 이 표현에서 심볼 x은 람다 추상화에 의해 바운드됩니다 λx.. 그러나 다른 상징 y은 구속력이 없으며 자유 롭다 . 우리는 그것이 무엇인지 모르는 그것이 어디에서 오는, 그래서 우리는 무엇을 모르는 의미 와 어떤 가치 가 나타내는, 우리는 무엇을 알아낼 때까지 그러므로 우리가 식을 계산할 수 없습니다 y의미.

사실, 같은 다른 두 개의 기호로 간다, 2+. 단지이 두 가지 기호에 대해 잘 알고 있기 때문에 일반적으로 컴퓨터가이를 알지 못한다는 사실을 잊어 버립니다. 예를 들어 라이브러리 나 언어 자체와 같이 어딘가에 정의하여 의미를 알려야합니다.

당신은 생각할 수 없는 그라고는 “주변 상황”에서, 표현 밖에, 다른 곳에서 정의 된 문자 환경을 . 환경은이 표현이 Qui-Gon Jinn이 말한 것처럼 : “항상 더 큰 물고기가 있습니다”;) 또는 일부 라이브러리 또는 언어 자체 ( 원시적 ) 로 표현되는 더 큰 표현 일 수 있습니다 .

이를 통해 람다 식을 두 가지 범주로 나눌 수 있습니다.

  • 닫힌 표현식 :이 표현식에서 발생하는 모든 심볼은 람다 추상화에 의해 구속 됩니다. 다시 말해서, 그들은 독립적입니다 ; 주변 상황을 평가할 필요가 없습니다. 그들은 또한 결합 자라고 불립니다 .
  • OPEN 표현식 : 이러한 표현식의 일부 기호는 바인딩 되지 않습니다. 즉, 해당 기호에서 발생하는 일부 기호는 비어 있으며 외부 정보가 필요하므로 이러한 기호의 정의를 제공 할 때까지 평가할 수 없습니다.

당신은 닫을 수 열린 공급에 의해 람다 식을 환경에 어떤 값으로 결합함으로써 모든 무료 문자 정의 (람다 일명 숫자, 문자열, 익명 함수 무엇이든간에 …).

그리고 여기에 온다 폐쇄 부분 : 폐쇄 (A)의 람다 식을 받는 값을주는 외부 환경 (환경)에 정의 된 심볼이 특정 세트입니다 무료 문자를 더 이상 그들을 비 무료 제작,이 표현에. 여전히 “정의되지 않은”자유 기호가 포함 된 열린 람다 식을 더 이상 자유 기호가없는 닫힌 기호로 바꿉니다 .

예를 들어, 다음과 같은 람다식이있는 경우 : λx.x/y+2기호 x는 바인딩되어 있고 기호 y는 비어 있지만, 의미가 open무엇인지 ( y및와 동일 +하며 2, 또한 동일 함) 를 말하지 않으면 식이 평가 될 수 없습니다 . 그러나 다음 과 같은 환경 이 있다고 가정 하십시오.

{  y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5  }

환경 우리의 람다 식의 모든 “정의되지 않은”(무료) 기호 공급 정의 ( y, +, 2), 그리고 몇 가지 추가 문자 ( q, w). 정의해야 할 기호는 환경의이 하위 집합입니다.

{  y: 3,
+: [built-in addition],
2: [built-in number]  }

그리고 이것은 정확히 우리의 람다 표현 의 종결 입니다 :>

즉, 열린 람다 식을 닫습니다 . 이것은 이름 폐쇄 가 처음부터 시작된 곳이며, 그래서이 스레드에서 많은 사람들의 대답이 정확하지 않은 이유입니다.


그들은 왜 착각합니까? 왜 많은 사람들이 클로저가 메모리의 일부 데이터 구조이거나 그들이 사용하는 언어의 일부 기능이라고 말하거나 클로저를 람다와 혼동하는 이유는 무엇입니까? :피

썬 / 오라클, 마이크로 소프트, 구글 등의 기업 시장은 이러한 구조를 그들의 언어 (자바, C #, Go 등)로 불렀기 때문에 책임을 져야한다. 그들은 종종 람다라고 여겨지는 것을 “폐쇄”라고 부릅니다. 또는 어휘 범위 지정을 구현하는 데 사용 된 특정 기술, 즉 함수가 정의시 외부 범위에 정의 된 변수에 액세스 할 수 있다는 사실을 “클로저”라고합니다. 그들은 종종 함수가 이러한 변수를 “닫는”즉, 외부 함수가 실행을 마친 후에 파괴되지 않도록 데이터 구조로 캡처한다고 말합니다. 그러나 이것은 사실 “민속 어원”과 마케팅으로 구성되어 있습니다.

그리고 그들이 말하는 내용에는 항상 약간의 진실이 있기 때문에 더 나쁩니다. 그러면 진실을 쉽게 거짓으로 무시할 수 없습니다.

람다를 일류 시민으로 사용하는 언어를 구현하려면 주변 환경에 정의 된 기호 (람다에서 자유 변수 사용)를 사용하도록 허용해야합니다. 그리고이 기호는 주변 함수가 반환 될 때에도 있어야합니다. 문제는 이러한 기호가 함수의 로컬 저장소 (일반적으로 호출 스택에 있음)에 바인딩되어 있으며 함수가 반환 될 때 더 이상 존재하지 않는다는 것입니다. 따라서 람다가 원하는 방식으로 작동하려면 이러한 자유 변수를 외부 컨텍스트에서 “캡처”하여 외부 컨텍스트가 사라질 때에도 나중에 저장해야합니다. 즉, 폐쇄 를 찾아야합니다.람다 (사용하는 모든 외부 변수)를 저장하고 다른 곳에 저장하십시오 (사본을 만들거나 스택이 아닌 다른 곳에서 미리 공간을 준비하여). 이 목표를 달성하기 위해 실제로 사용하는 방법은 언어의 “구현 세부 사항”입니다. 여기서 중요한 것은 클로저 인데, 이것은 람다 환경 에서 자유 변수 가 저장되어 어딘가에 저장해야합니다.

사람들이 언어를 구현할 때 사용하는 실제 데이터 구조를 호출하여 클로저를 “클로저”자체로 구현하는 데 너무 오래 걸리지 않았습니다. 구조는 일반적으로 다음과 같습니다.

Closure {
   [pointer to the lambda function's machine code],
   [pointer to the lambda function's environment]
}

이러한 데이터 구조는 다른 함수에 매개 변수로 전달되고 함수에서 리턴되며 변수에 저장되어 람다를 나타내며 해당 환경에서 실행할 기계 코드뿐만 아니라 주변 환경에 액세스 할 수 있습니다. 그러나 그것은 단지 할 수있는 방법 (중 하나)의 구현 폐쇄하지 폐쇄 자체를.

위에서 설명한 것처럼 람다 식의 닫힘은 해당 람다 식에 포함 된 자유 변수에 값을 제공하여 식을 효과적으로 닫는 ( 아직 평가할 수없는 열린 람다 식을 변환하는) 정의의 하위 집합입니다. 폐쇄 그 안에 포함 된 모든 심볼들은 현재 정의되어 있기 때문에 다음, 평가할 수 람다 식).

다른 개념은 프로그래머와 언어 공급 업체의 “카고 컬트”와 “부두 매직”으로, 이러한 개념의 실제 뿌리를 알지 못합니다.

귀하의 질문에 답변이 되었기를 바랍니다. 그러나 후속 질문이 있으시면 언제든지 의견을 물어보십시오. 더 잘 설명하려고 노력할 것입니다.


답변

대부분의 사람들은 함수 를 생각할 때 명명 된 함수 를 생각 합니다 .

function foo() { return "This string is returned from the 'foo' function"; }

이들은 물론 이름으로 불립니다.

foo(); //returns the string above

람다 식을 사용하면 익명 함수를 가질 수 있습니다 .

 @foo = lambda() {return "This is returned from a function without a name";}

위의 예제에서 할당 된 변수를 통해 람다를 호출 할 수 있습니다.

foo();

그러나 익명 함수를 변수에 할당하는 것보다 더 유용한 것은 변수를 상위 함수, 즉 다른 함수를 수락 / 반환하는 함수에 전달하거나 전달하는 것입니다. 이러한 많은 경우에 함수 이름 지정은 불필요합니다.

function filter(list, predicate)
 { @filteredList = [];
   for-each (@x in list) if (predicate(x)) filteredList.add(x);
   return filteredList;
 }

//filter for even numbers
filter([0,1,2,3,4,5,6], lambda(x) {return (x mod 2 == 0)});

폐쇄 명명 또는 익명 기능하지만, 그 자체가 공지되어 될 수있다 때 기능 즉, 폐쇄가 여전히에서 사용되는 모든 외부 변수 환경 참조되며, 정의 된 범위의 변수 그것을 “위에 폐쇄” 폐쇄 자체. 명명 된 폐쇄는 다음과 같습니다.

@x = 0;

function incrementX() { x = x + 1;}

incrementX(); // x now equals 1

그다지 좋아 보이지는 않지만 이것이 모두 다른 함수에 있고 incrementX외부 함수에 전달 되면 어떻게 될까요?

function foo()
 { @x = 0;

   function incrementX()
    { x = x + 1;
      return x;
    }

   return incrementX;
 }

@y = foo(); // y = closure of incrementX over foo.x
y(); //returns 1 (y.x == 0 + 1)
y(); //returns 2 (y.x == 1 + 1)

이것이 함수형 프로그래밍에서 상태 저장 객체를 얻는 방법입니다. “incrementX”라는 이름을 지정할 필요가 없으므로이 경우 람다를 사용할 수 있습니다.

function foo()
 { @x = 0;

   return lambda()
           { x = x + 1;
             return x;
           };
 }


답변

모든 클로저가 람다 인 것은 아니며 모든 람다가 클로저 인 것은 아닙니다. 둘 다 기능이지만 반드시 익숙한 방식은 아닙니다.

람다는 본질적으로 함수를 선언하는 표준 방법이 아니라 인라인으로 정의 된 함수입니다. 람다는 종종 객체로 전달 될 수 있습니다.

클로저는 본문 외부의 필드를 참조하여 주변 상태를 둘러싸는 함수입니다. 닫힌 상태는 닫힌 호출을 통해 계속 유지됩니다.

객체 지향 언어에서 클로저는 일반적으로 객체를 통해 제공됩니다. 그러나 일부 OO 언어 (예 : C #)는 상태를 둘러싸는 객체가없는 순수 기능 언어 (예 : lisp)가 제공하는 클로저 정의에 더 가까운 특수 기능을 구현 합니다.

흥미로운 점은 C #에 Lambdas and Closures가 도입되면서 기능 프로그래밍이 주류 사용에 더 가까워진다는 것입니다.


답변

다음과 같이 간단합니다. lambda는 언어 구성, 즉 단순히 익명 함수에 대한 구문입니다. 클로저는이를 구현하는 기술 또는 그 문제에 대해 이름이 있거나 익명 인 모든 일류 함수입니다.

보다 정확하게는 클로저는 런타임에 퍼스트 클래스 함수 가 “코드”와 해당 코드에 사용 된 모든 비 로컬 변수에 대해 “닫는”쌍으로 표현되는 방식입니다. 이런 식으로 변수가 시작된 외부 범위가 이미 종료 된 경우에도 해당 변수에 여전히 액세스 할 수 있습니다.

불행히도, 함수에는 일급 값으로 기능을 지원하지 않거나 주름진 형태로만 지원하는 많은 언어가 있습니다. 따라서 사람들은 종종 “실제”라는 용어를 “실제”를 구별하기 위해 사용합니다.


답변

프로그래밍 언어의 관점에서 보면 완전히 다른 두 가지입니다.

기본적으로 Turing 완전한 언어의 경우 추상화, 적용 및 축소와 같이 매우 제한된 요소 만 필요합니다. 추상화 및 응용 프로그램은 lamdba 식을 구축 할 수있는 방법을 제공하며 축소는 람다 식의 의미를 결정합니다.

Lambda는 계산 프로세스를 추상화 할 수있는 방법을 제공합니다. 예를 들어, 두 숫자의 합을 계산하기 위해 두 개의 매개 변수 x, y를 가져 와서 x + y를 반환하는 프로세스를 추상화 할 수 있습니다. 계획에서는 다음과 같이 쓸 수 있습니다.

(lambda (x y) (+ x y))

매개 변수의 이름을 바꿀 수는 있지만 완료 한 작업은 변경되지 않습니다. 거의 모든 프로그래밍 언어에서 람다 식에 함수라는 이름을 지정할 수 있습니다. 그러나 큰 차이는 없지만 개념적으로 구문 설탕으로 간주 될 수 있습니다.

이제 이것이 어떻게 구현 될 수 있는지 상상해보십시오. 람다 식을 일부 식에 적용 할 때마다

((lambda (x y) (+ x y)) 2 3)

매개 변수를 평가할 표현식으로 간단히 대체 할 수 있습니다. 이 모델은 이미 매우 강력합니다. 그러나이 모델을 사용하면 기호 값을 변경할 수 없습니다. 예를 들어 상태 변경을 모방 할 수 없습니다. 따라서 더 복잡한 모델이 필요합니다. 간단히 말해서 람다 식의 의미를 계산할 때마다 기호 쌍과 해당 값을 환경 (또는 테이블)에 넣습니다. 그런 다음 표에서 해당 기호를 찾아 나머지 (+ xy)를 평가합니다. 이제 환경에서 직접 작동 할 기본 요소를 제공하면 상태 변경을 모델링 할 수 있습니다!

이 배경으로 다음 기능을 확인하십시오.

(lambda (x y) (+ x y z))

우리는 람다 식을 평가할 때 xy가 새로운 테이블에 묶일 것이라는 것을 알고 있습니다. 그러나 어떻게 그리고 어디에서 z를 찾을 수 있습니까? 실제로 z는 자유 변수라고합니다. z가 포함 된 외부 환경이 있어야합니다. 그렇지 않으면 표현식의 의미는 x와 y를 바인딩하여 결정할 수 없습니다. 이를 분명히하기 위해 구성표에 다음과 같이 무언가를 쓸 수 있습니다.

((lambda (z) (lambda (x y) (+ x y z))) 1)

따라서 z는 외부 테이블에서 1에 바인딩됩니다. 우리는 여전히 두 개의 매개 변수를 허용하는 함수를 얻지 만 그 의미는 외부 환경에 달려 있습니다. 다시 말해, 외부 환경은 자유 변수로 닫힙니다. set!의 도움으로 함수 상태를 상태화할 수 있습니다. 즉 수학적인 의미의 함수가 아닙니다. 그것이 반환하는 것은 입력뿐만 아니라 z에도 달려 있습니다.

이것은 당신이 이미 잘 알고있는 것입니다. 객체의 방법은 거의 항상 객체의 상태에 의존합니다. 그렇기 때문에 어떤 사람들은 “폐쇄는 가난한 사람의 물건”이라고 말합니다. 그러나 우리는 일류 함수를 정말로 좋아하기 때문에 물건을 가난한 사람의 닫힌 것으로 간주 할 수도 있습니다.

나는 그 체계로 인해 아이디어를 설명하기 위해 체계를 사용합니다.이 체계는 실제로 가장 가까운 언어 중 하나입니다. 여기에있는 모든 자료는 SICP 3 장에 훨씬 더 잘 소개되어 있습니다.

요약하면 람다와 클로저는 실제로 다른 개념입니다. 람다는 함수입니다. 클로저는 람다와 람다를 닫는 해당 환경의 쌍입니다.


답변

개념은 위에서 설명한 것과 동일하지만 PHP 배경 출신이라면 PHP 코드를 사용하여 자세히 설명합니다.

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

함수 ($ v) {return $ v> 2; }은 람다 함수 정의입니다. 변수에 저장할 수도 있으므로 재사용 할 수 있습니다.

$max = function ($v) { return $v > 2; };

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);

필터링 된 배열에서 허용되는 최대 수를 변경하려면 어떻게해야합니까? 다른 람다 함수를 작성하거나 클로저를 작성해야합니다 (PHP 5.3).

$max_comp = function ($max) {
  return function ($v) use ($max) { return $v > $max; };
};

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comp(2));

클로저는 자체 환경에서 평가되는 함수로, 함수가 호출 될 때 액세스 할 수있는 하나 이상의 바운드 변수가 있습니다. 그것들은 많은 개념이 작용하는 기능적 프로그래밍 세계에서 왔습니다. 클로저는 람다 함수와 비슷하지만 클로저가 정의 된 외부 환경의 변수와 상호 작용할 수 있다는 점에서 더 똑똑합니다.

다음은 PHP 클로저의 간단한 예입니다.

$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

이 기사에서 잘 설명했습니다.