매우 빠르게 : "like like to replace like"인 경우 대체는 "referentially transparent"이고 모든 효과가 반환 값에 포함 된 경우 함수는 "pure"입니다. 두 가지 모두 정확하게 만들 수 있지만, 동일하지 않거나 서로를 암시하는 것도 중요합니다.
이제 폐쇄에 대해 이야기합시다.
지루한 (주로 순수한) "폐쇄 물"
람다 용어를 평가할 때 변수를 환경 조회로 해석하기 때문에 클로저가 발생합니다. 따라서 평가 결과로 람다 항을 반환하면 변수 내부의 변수가 정의 될 때 가져온 값을 "닫습니다".
평범한 람다 미적분학에서 이것은 사소한 것이며 전체 개념은 사라집니다. 이를 입증하기 위해 다음은 비교적 가벼운 람다 미적분 해석기입니다.
-- untyped lambda calculus values are functions
data Value = FunVal (Value -> Value)
-- we write expressions where variables take string-based names, but we'll
-- also just assume that nobody ever shadows names to avoid having to do
-- capture-avoiding substitutions
type Name = String
data Expr
= Var Name
| App Expr Expr
| Abs Name Expr
-- We model the environment as function from strings to values,
-- notably ignoring any kind of smooth lookup failures
type Env = Name -> Value
-- The empty environment
env0 :: Env
env0 _ = error "Nope!"
-- Augmenting the environment with a value, "closing over" it!
addEnv :: Name -> Value -> Env -> Env
addEnv nm v e nm' | nm' == nm = v
| otherwise = e nm
-- And finally the interpreter itself
interp :: Env -> Expr -> Value
interp e (Var name) = e name -- variable lookup in the env
interp e (App ef ex) =
let FunVal f = interp e ef
x = interp e ex
in f x -- application to lambda terms
interp e (Abs name expr) =
-- augmentation of a local (lexical) environment
FunVal (\value -> interp (addEnv name value e) expr)
주목해야 할 중요한 부분 addEnv
은 새로운 이름으로 환경을 확장 할 때입니다. 이 함수는 해석 된 Abs
견인 항 (람다 항) 의 "내부"만 호출됩니다 . Var
용어를 평가할 때마다 환경이 "검색" 되므로 해당 용어 는 을 포함하는 견인에 의해 포착 된 Var
모든 항목으로 해석됩니다 .Name
Env
Abs
Var
이제 다시 일반 LC 용어로 이것은 지루합니다. 그것은 바운드 변수가 누군가가 관심을 갖는 한 상수라는 것을 의미합니다. 이들은 환경에서 해당 시점까지의 어휘 범위로 표시 한 값으로 직접 및 즉시 평가됩니다.
이것은 또한 (거의) 순수합니다. 람다 미적분학에서 용어의 유일한 의미는 반환 값에 의해 결정됩니다. 유일한 예외는 Omega 용어로 구체화되는 비 종료의 부작용입니다.
-- in simple LC syntax:
--
-- (\x -> (x x)) (\x -> (x x))
omega :: Expr
omega = App (Abs "x" (App (Var "x")
(Var "x")))
(Abs "x" (App (Var "x")
(Var "x")))
재미있는 (불순한) 폐쇄
이제 특정 배경에서 우리가 닫은 변수와 상호 작용할 수 없다는 개념이 없기 때문에 위의 일반 LC에 설명 된 폐쇄는 지루합니다. 특히 "클로저"라는 단어는 다음 Javascript와 같은 코드를 호출하는 경향이 있습니다.
> function mk_counter() {
var n = 0;
return function incr() {
return n += 1;
}
}
undefined
> var c = mk_counter()
undefined
> c()
1
> c()
2
> c()
3
이것은 n
내부 함수에서 변수를 닫았으며 incr
호출 incr
이 해당 변수와 의미있게 상호 작용 한다는 것을 보여줍니다 . mk_counter
순수하지만 incr
결정적으로 불완전합니다 (참조 적으로 투명하지 않음).
이 두 인스턴스의 차이점은 무엇입니까?
"가변"의 개념
우리가 평범한 LC 의미에서 대체와 추상화가 무엇을 의미하는지 살펴보면, 그것들은 분명히 평범하다는 것을 알 수 있습니다. 변수는 말 그대로 즉각적인 환경 조회에 지나지 않습니다 . 람다 추상화는 말 그대로없는 것이 더 내부 표현을 평가하기 위해 증강 환경을 만드는 것보다. 이 모델 에는 변형이 허용되지 않기 때문에 mk_counter
/로 보았던 행동의 종류에 대한 incr
여지가 없습니다.
많은 사람들에게 이것은 "가변"의 의미, 변화의 핵심입니다. 그러나 의미 론자들은 LC에 사용 된 변수의 종류와 Javascript에 사용 된 "변수"의 종류를 구별하려고합니다. 그렇게하기 위해, 이들은 후자를 "돌연변이 가능한 셀"또는 "슬롯"이라고 부르는 경향이있다.
"+ X X"를 허용하지 않는다 (수학) 표현 :이 용어는 더 "알 수없는"같은 것을 의미 수학에서 "변수"의 긴 사용 기록을 다음 x
시간에 따라 변화하는, 그 대신에 관계없이 의미가있는 것을 의미한다 (단일, 상수) 값 x
이 사용됩니다.
따라서 슬롯에 값을 넣고 빼는 기능을 강조하기 위해 "슬롯"이라고합니다.
혼란을 더하기 위해 Javascript에서 이러한 "슬롯"은 변수와 동일하게 보입니다.
var x;
하나를 작성하고 우리가 쓸 때
x;
해당 슬롯에 현재 저장된 값을 찾는다는 의미입니다. 이를 더 명확하게하기 위해 순수한 언어는 슬롯을 이름을 (수학적, 람다 미적분학) 이름으로 생각하는 경향이 있습니다. 이 경우 슬롯에서 가져 오거나 넣을 때 명시 적으로 레이블을 지정해야합니다. 이러한 표기법은 다음과 같은 경향이 있습니다.
-- create a fresh, empty slot and name it `x` in the context of the
-- expression E
let x = newSlot in E
-- look up the value stored in the named slot named `x`, return that value
get x
-- store a new value, `v`, in the slot named `x`, return the slot
put x v
이 표기법의 장점은 이제 우리가 수학 변수와 가변 슬롯을 확고하게 구분한다는 것입니다. 변수는 슬롯을 값으로 사용할 수 있지만 변수에 의해 명명 된 특정 슬롯은 해당 범위에서 일정합니다.
이 표기법을 사용하여 mk_counter
예제를 다시 작성할 수 있습니다 (이번에는 Haskell과 유사한 구문이지만 Haskell과 같은 의미론으로 결정됨).
mkCounter =
let x = newSlot
in (\() -> let old = get x
in get (put x (old + 1)))
이 경우 우리는이 가변 슬롯을 조작하는 절차를 사용하고 있습니다. 그것을 구현하기 위해서는 다음과 같은 이름의 일정한 환경뿐만 아니라 폐쇄해야합니다.x
뿐만 아니라 필요한 모든 슬롯이 포함 된 가변 환경 합니다. 이것은 사람들이 너무 좋아하는 "폐쇄"라는 일반적인 개념에 더 가깝습니다.
다시 말하지만, mkCounter
매우 불순합니다. 또한 참조 적으로 불투명합니다. 그러나 통지 부작용 이름 캡처 또는 폐쇄 대신의 캡처에서 발생하지 않도록 변경할 세포 와 같은 그것의 측면 초래 운영 get
하고 put
.
궁극적으로 이것이 귀하의 질문에 대한 최종 답변이라고 생각합니다. 순도는 (수학적) 변수 캡처의 영향을받지 않지만 대신 캡처 된 변수로 명명 된 가변 슬롯에서 수행되는 부작용으로 인해 발생합니다.
LC에 가까워 지거나 순도를 유지하려고 시도하지 않는 언어에서만이 두 개념이 너무 자주 혼동되어 혼란을 초래합니다.