누군가 설명해 주시겠습니까? 나는 기본 개념을 이해하지만 종종 상호 교환 적으로 사용되는 것을 보았고 혼란스러워합니다.
그리고 우리가 여기 왔으니, 그것들은 일반 기능과 어떻게 다릅니 까?
누군가 설명해 주시겠습니까? 나는 기본 개념을 이해하지만 종종 상호 교환 적으로 사용되는 것을 보았고 혼란스러워합니다.
그리고 우리가 여기 왔으니, 그것들은 일반 기능과 어떻게 다릅니 까?
답변:
람다 단지 익명 함수입니다 - 아니 이름으로 정의 된 함수. 구성표와 같은 일부 언어에서는 명명 된 함수와 동일합니다. 실제로 함수 정의는 람다를 변수에 내부적으로 바인딩하여 다시 작성됩니다. 파이썬과 같은 다른 언어에서는 서로간에 (필요하지 않은) 구별이 있지만 그렇지 않으면 같은 방식으로 동작합니다.
폐쇄 어떤 함수 를 통해 폐쇄 환경 이 정의시킨. 이는 매개 변수 목록에없는 변수에 액세스 할 수 있음을 의미합니다. 예 :
def func(): return h
def anotherfunc(h):
return func()
때문에, 오류가 발생합니다 func
하지 않는 이상 주변 에서 환경 anotherfunc
- h
정의되지 않는다. func
지구 환경을 폐쇄합니다. 이것은 작동합니다 :
def anotherfunc(h):
def func(): return h
return func()
여기에 func
가 정의되어 anotherfunc
있으며 파이썬 2.3 이상 (또는 이와 비슷한 숫자) 에서 클로저가 거의 올바르게 수정 되면 (돌연변이는 여전히 작동하지 않음), 이는 환경을 닫고 anotherfunc
내부 변수에 액세스 할 수 있음을 의미합니다 그것. 사용할 때 파이썬에서 3.1, 돌연변이도 작동 키워드를 .nonlocal
또 다른 중요한 점 func
은 anotherfunc
더 이상 평가되지 않더라도 환경을 계속 폐쇄하는 것 입니다 anotherfunc
. 이 코드는 다음과 같이 작동합니다.
def anotherfunc(h):
def func(): return h
return func
print anotherfunc(10)()
10이 인쇄됩니다.
아시다시피 람다 와는 아무런 관련이 없습니다. 두 가지 다른 개념이지만 서로 관련이 있습니다.
이 StackOverflow 질문에 대한 답변에서도 람다와 클로저에 대해 많은 혼란이 있습니다. 특정 프로그래밍 언어 또는 다른 단서가없는 프로그래머와의 연습에서 클로저에 대해 배운 임의의 프로그래머에게 묻는 대신 소스로 이동하십시오 (모두 시작된 곳). 람다와 클로저는 최초의 전자 컴퓨터가 존재하기 전인 30 년대에 알론조 교회가 발명 한 람다 미적분학 (Lamda Calculus) 에서 나왔기 때문에 이것이 제가 이야기 하고있는 출처 입니다.
람다 미적분학은 세계에서 가장 간단한 프로그래밍 언어입니다. 당신이 할 수있는 유일한 것 : ►
f x
합니다. f
함수이고, x
그것의 유일한 파라미터이다)λ
(람다), 기호 이름 (예 x
:) .
, 식 앞에 점을 붙여서 수행합니다. 그러면 표현식을 하나의 매개 변수를 기대 하는 함수 로 변환합니다 . 예를 들어 : 표현식 을 가져 와서이 표현식 의 기호 가 바운드 변수 임을 나타냅니다. 매개 변수로 제공 한 값으로 대체 할 수 있습니다.
이 방법으로 정의 된 함수는 익명입니다.λx.x+2
x+2
x
(λx.x+2) 7
. 그런 다음 적용된 람다 의 하위 표현식에서와 7
같이 표현식 (이 경우 리터럴 값) 이 대체 되므로 일반적인 산술 규칙에 따라 줄어 듭니다 .x
x+2
7+2
9
그래서 우리는 수수께끼 중 하나를 해결했습니다.
lambda 는 위 예제 의 익명 함수 입니다 λx.x+2
.
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 )
내가 말했듯이, 람다 추상화는 하위 표현에 기호를 바인딩 하여 대체 가능한 매개 변수가 됩니다. 이러한 기호를 bound 라고 합니다. 그러나 표현에 다른 상징이 있다면 어떨까요? 예를 들면 다음과 같습니다 λx.x/y+2
.. 이 표현에서 심볼 x
은 람다 추상화에 의해 바운드됩니다 λx.
. 그러나 다른 상징 y
은 구속력이 없으며 자유 롭다 . 우리는 그것이 무엇인지 모르는 그것이 어디에서 오는, 그래서 우리는 무엇을 모르는 의미 와 어떤 가치 가 나타내는, 우리는 무엇을 알아낼 때까지 그러므로 우리가 식을 계산할 수 없습니다 y
의미.
사실, 같은 다른 두 개의 기호로 간다, 2
와 +
. 단지이 두 가지 기호에 대해 잘 알고 있기 때문에 일반적으로 컴퓨터가이를 알지 못한다는 사실을 잊어 버립니다. 예를 들어 라이브러리 나 언어 자체와 같이 어딘가에 정의하여 의미를 알려야합니다.
당신은 생각할 수 없는 그라고는 "주변 상황"에서, 표현 밖에, 다른 곳에서 정의 된 문자 환경을 . 환경은이 표현이 Qui-Gon Jinn이 말한 것처럼 : "항상 더 큰 물고기가 있습니다";) 또는 일부 라이브러리 또는 언어 자체 ( 원시적 ) 로 표현되는 더 큰 표현 일 수 있습니다 .
이를 통해 람다 식을 두 가지 범주로 나눌 수 있습니다.
당신은 닫을 수 열린 공급에 의해 람다 식을 환경에 어떤 값으로 결합함으로써 모든 무료 문자 정의 (람다 일명 숫자, 문자열, 익명 함수 무엇이든간에 ...).
그리고 여기에 온다 폐쇄 부분 : 폐쇄 (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();
이 질문은 오래되었고 많은 답변을 얻었습니다.
이제는 비공식 폐쇄 프로젝트 인 Java 8 및 Official Lambda를 사용하여 질문을 되살 렸습니다.
Java 컨텍스트에서의 답변 ( Lambdas 및 클로저 를 통해 -차이점은 무엇입니까? ) :
"클로저는 각 자유 변수를 값에 바인딩하는 환경과 쌍을 이루는 람다 식입니다. Java에서는 람다식이 클로저를 통해 구현되므로 두 용어가 커뮤니티에서 상호 교환 가능하게 사용되었습니다."
간단히 말해서, 클로저는 범위에 대한 트릭이며 람다는 익명의 함수입니다. 우리는 람다로 더 우아하게 닫을 수 있고 람다는 종종 더 높은 함수에 전달되는 매개 변수로 사용됩니다
Lambda 표현식은 익명 함수일뿐입니다. 예를 들어 일반 자바에서는 다음과 같이 작성할 수 있습니다.
Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
public Job apply(Person person) {
Job job = new Job(person.getPersonId(), person.getJobDescription());
return job;
}
};
여기서 Function 클래스는 Java 코드로 작성되었습니다. 이제 당신은 mapPersonToJob.apply(person)
그것을 사용하기 위해 어딘가에 전화 할 수 있습니다 . 그게 단지 하나의 예입니다. 그것은 구문이 있기 전에 람다입니다. 람다는 바로 가기입니다.
폐쇄:
Lambda는이 범위 밖의 변수에 액세스 할 수있을 때 클로저가됩니다. 나는 당신이 그 마법을 말할 수 있다고 생각합니다. 마 법적으로 그것이 만들어진 환경을 감싸고 범위 밖에서 변수를 사용할 수 있습니다 (외부 범위입니다. 그래서 클로저는 람다가 외부 범위에 액세스 할 수 있음을 의미합니다.
Kotlin에서 람다는 항상 해당 폐쇄 (외부 범위에있는 변수)에 액세스 할 수 있습니다.
함수가 외부 변수를 사용하여 작업을 수행하는지 여부에 따라 다릅니다.
외부 변수 -함수 범위 밖에서 정의 된 변수
Lambda 식은 작업을 수행하기위한 매개 변수, 내부 변수 또는 상수에 의존하므로 상태 비 저장 입니다.
Function<Integer,Integer> lambda = t -> {
int n = 2
return t * n
}
클로저 는 외부 변수 (예 : 함수 본문의 범위 밖에서 정의 된 변수)와 매개 변수 및 상수를 사용하여 작업을 수행하기 때문에 상태를 유지 합니다.
int n = 2
Function<Integer,Integer> closure = t -> {
return t * n
}
Java가 클로저를 작성할 때 변수 n을 함수와 함께 유지하므로 다른 함수에 전달되거나 어디서나 사용될 때 참조 될 수 있습니다.