Çok çabuk: bir ikame, "benzer ikame benzerine yol açarsa" ise "referans olarak saydamdır" ve eğer tüm etkileri geri dönüş değerinde ise, bir işlev "saftır". Her ikisi de kesin olarak yapılabilir, ancak aynı olmadıklarını ve hatta birinin diğerini ima etmediğini not etmek çok önemlidir.
Şimdi kapaklar hakkında konuşalım.
Sondaj (çoğunlukla saf) "kapaklar"
Kapamalar bir lambda terimini değerlendirirken değişkenleri çevre aramaları olarak yorumladığımız için oluşur. Dolayısıyla, bir değerlendirme sonucu bir lambda terimi döndürdüğümüzde, içindeki değişkenler tanımlandıklarında aldıkları değerleri "kapatır".
Düz lambda matematiğinde bu önemsiz bir şeydir ve bütün kavram ortadan kalkar. Bunu göstermek için, işte göreceli olarak hafif bir lambda hesabı tercümanı:
-- 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
Dikkat edilmesi gereken önemli nokta , çevreyi yeni bir adla genişlettiğimizde. Bu fonksiyon, yorumlanmış Abs
çekiş teriminin yalnızca "iç" olarak adlandırılır (lambda terimi). Çevre biz değerlendirmek ne zaman "baktım" alır Var
vadeli ve bu nedenle Var
s çözmek ne kadar Name
atıfta Env
hangi tarafından yakalanan var Abs
içeren çekiş Var
.
Şimdi, yine, düz LC terimlerinde bu çok sıkıcı. Bağımlı değişkenlerin, herkesin umursadığı ölçüde sabit olduğu anlamına gelir. Doğrudan ve hemen, çevrede belirttikleri değerler, o noktaya kadar sözcüksel olarak kapsamlaştırıldığı gibi değerlendirilir.
Bu da (neredeyse) saf. Bizim lambda hesabımızdaki herhangi bir terimin tek anlamı, geri dönüş değeri ile belirlenir. Tek istisna, Omega terimi tarafından somutlaştırılan fesih olmamanın yan etkisidir:
-- 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")))
İlginç (saf olmayan) kapanışlar
Şimdi, belirli geçmişlere göre, yukarıda düz LC'de açıklanan kapaklar sıkıcıdır, çünkü kapattığımız değişkenlerle etkileşime girme fikri yoktur. Özellikle, "kapatma" kelimesi aşağıdaki Javascript gibi bir kod çağırır.
> function mk_counter() {
var n = 0;
return function incr() {
return n += 1;
}
}
undefined
> var c = mk_counter()
undefined
> c()
1
> c()
2
> c()
3
Bu n
, iç işlevdeki değişkeni kapattığımızı incr
ve çağrının incr
bu değişkenle anlamlı bir şekilde etkileşime girdiğimizi gösterir . mk_counter
saf, ancak incr
kesin olarak kesin değildir (ve referans olarak şeffaf değildir).
Bu iki örnek arasında ne fark var?
"Değişken" kavramları
Düz LC anlamında yer değiştirme ve soyutlamanın ne anlama geldiğine bakarsak, kesinlikle düz olduklarını fark ediyoruz. Değişkenler tam anlamıyla acil ortam aramalarından başka bir şey değildir . Lambda soyutlama kelimenin tam anlamıyla iç ifadeyi değerlendirmek için artırılmış bir ortam yaratmaktan başka bir şey değildir . Bu modelde gördüğümüz davranış türüne yer yok mk_counter
/ var incr
çünkü izin verilebilecek bir değişiklik yok.
Birçokları için bu, "değişken" in ne anlama geldiğinin kalbidir - çeşitlilik. Bununla birlikte, anlambilimciler, LC'de kullanılan değişken türüyle Javascript'te kullanılan "değişken" türünü birbirinden ayırmayı severler. Bunu yapmak için, ikincisini "değiştirilebilir hücre" veya "yarık" olarak adlandırmaya meyillidirler.
Bu terminoloji, "değişken" in "bilinmeyen" gibi bir şey ifade ettiği matematikteki uzun tarihsel kullanımını izler: "x + x" (matematiksel) ifadesi x
zaman içinde değişime izin vermez , bunun yerine anlamsız olması gerekir. (tek, sabit) değerinin x
alır.
Bu nedenle, değerleri bir yuvaya koyma ve bunları dışarıya çıkarma yeteneğini vurgulamak için “yuva” diyoruz.
Kargaşayı daha da eklemek için Javascript'te bu "slotlar" değişkenlerle aynı gözüküyor:
var x;
birini oluşturmak ve sonra yazarken
x;
o anda o yuvada depolanmış olan değeri aradığımızı gösterir. Bunu daha net hale getirmek için, saf diller slotları (matematiksel, lambda matematiği) isimleri olarak alan isimler olarak kabul etme eğilimindedir. Bu durumda, bir slottan çıktığımızda veya çıkardığımızda açıkça etiketlemeliyiz. Böyle bir gösterim gibi görünmek eğilimindedir
-- 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
Bu gösterimin avantajı, şimdi matematiksel değişkenler ve değişken yuvalar arasında kesin bir ayrım yapmamızdır. Değişkenler, değerleri değer olarak alabilir, ancak değişken tarafından adlandırılan belirli alan, kapsamı boyunca sabittir.
Bu gösterimi kullanarak mk_counter
örneği yeniden yazabiliriz (bu sefer Haskell benzeri bir anlambilimine karar vermemiş olsa da Haskell benzeri bir sözdiziminde):
mkCounter =
let x = newSlot
in (\() -> let old = get x
in get (put x (old + 1)))
Bu durumda, bu değişken yuvayı manipüle eden prosedürleri kullanıyoruz. Uygulayabilmek için, yalnızca sabit bir ad ortamını x
değil aynı zamanda gerekli tüm yuvaları içeren değişken bir ortamı kapatmamız gerekir . Bu, ortak "kapatma" kavramına daha yakın olan insanlar çok seviyor.
Yine, mkCounter
çok saf değil. Aynı zamanda referans olarak opaktır. Ama bildirim yan etkileri adı yakalama veya kapatılması yerine yakalanması kaynaklanan olmadığını değişken hücreye ve benzeri üzerinde yan etkileyen operasyonlar get
ve put
.
Sonuçta, bunun sorunuzun son cevabı olduğunu düşünüyorum: saflık (matematiksel) değişken yakalamadan etkilenmez, bunun yerine yakalanan değişkenler tarafından adlandırılan değişken yuvalarda gerçekleştirilen yan etkileyici işlemlerden etkilenir.
Sadece, LC'ye yakın olmaya çalışmayan veya bu iki kavramın sık sık karıştığı karışıklığı sağlayacak saflığı korumaya çalışmayan dillerde.