เร็วมาก: การทดแทนคือ "โปร่งใสแบบอ้างอิงได้" ถ้า "การทดแทนเช่นนำไปสู่การชอบ" และฟังก์ชั่นคือ "บริสุทธิ์" หากเอฟเฟกต์ทั้งหมดมีอยู่ในค่าส่งคืน ทั้งสองอย่างนั้นสามารถสร้างได้อย่างแม่นยำ แต่เป็นสิ่งสำคัญที่จะต้องทราบว่าพวกเขาไม่เหมือนกัน
ตอนนี้เรามาพูดเกี่ยวกับการปิด
น่าเบื่อ (ส่วนใหญ่บริสุทธิ์) "ปิด"
การปิดเกิดขึ้นเนื่องจากเมื่อเราประเมินคำแลมบ์ดาเราจะตีความตัวแปรที่ถูกผูกไว้ว่าเป็นการค้นหาสภาพแวดล้อม ดังนั้นเมื่อเรากลับคำแลมบ์ดาซึ่งเป็นผลมาจากการประเมินตัวแปรภายในจะมี "ปิด" ค่าที่พวกเขาใช้เมื่อมันถูกกำหนด
ในแคลคูลัสแลมบ์ดาธรรมดานี่เป็นเรื่องเล็กน้อยและความคิดทั้งหมดก็หายไป เพื่อแสดงให้เห็นว่านี่เป็นล่ามแคลคูลัสแลมบ์ดาที่ค่อนข้างเบา:
-- 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 ธรรมดามันน่าเบื่อ หมายความว่าตัวแปรที่ถูกผูกไว้เป็นค่าคงที่เท่าที่ทุกคนใส่ใจ พวกเขาได้รับการประเมินโดยตรงและทันทีที่ค่าที่พวกเขาแสดงในสภาพแวดล้อมที่ขอบเขตขอบเขตจนถึงจุดนั้น
นี่ก็บริสุทธิ์ (เกือบ) ด้วย ความหมายเฉพาะของคำใด ๆ ในแคลคูลัสแลมบ์ดาของเรานั้นพิจารณาจากผลตอบแทน ข้อยกเว้นเพียงอย่างเดียวคือผลข้างเคียงของการไม่สิ้นสุดซึ่งเป็นตัวเป็นตนโดยเทอมโอเมก้า:
-- 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
เพราะไม่มีการเปลี่ยนแปลงที่อนุญาต
สำหรับหลาย ๆ คนนี่คือหัวใจของ "ตัวแปร" หมายถึง - การเปลี่ยนแปลง อย่างไรก็ตาม semanticists ต้องการแยกความแตกต่างระหว่างชนิดของตัวแปรที่ใช้ใน 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-like แม้ว่าจะมีความหมายเหมือน Un-Haskell เหมือน semantics):
mkCounter =
let x = newSlot
in (\() -> let old = get x
in get (put x (old + 1)))
ในกรณีนี้เรากำลังใช้โพรซีเดอร์ที่จัดการสล็อตที่ไม่แน่นอนนี้ เพื่อที่จะนำไปใช้เราจำเป็นต้องปิดไม่เพียง แต่สภาพแวดล้อมคงที่ของชื่อเช่นx
เท่านั้น แต่ยังรวมถึงสภาพแวดล้อมที่ไม่แน่นอนที่มีช่องที่ต้องการทั้งหมด นี่คือความใกล้ชิดกับความคิดทั่วไปของคน "ปิด" รักมาก
อีกครั้งmkCounter
ไม่บริสุทธิ์มาก มันยังทึบแสง referential มาก แต่แจ้งให้ทราบว่าผลข้างเคียงที่ไม่ได้เกิดขึ้นจากการจับภาพชื่อหรือปิด แต่แทนที่จะจับของมือถือที่ไม่แน่นอนและการดำเนินงานด้านผลต่อมันเหมือนและget
put
ในที่สุดฉันคิดว่านี่เป็นคำตอบสุดท้ายสำหรับคำถามของคุณ: ความบริสุทธิ์ไม่ได้รับผลกระทบจากการจับตัวแปร (ทางคณิตศาสตร์) แต่แทนที่ด้วยการดำเนินการด้านผลกระทบที่ดำเนินการกับช่องที่ไม่แน่นอนที่ตั้งชื่อโดยตัวแปรที่บันทึก
เฉพาะในภาษาที่ไม่ได้พยายามที่จะใกล้เคียงกับ LC หรือไม่พยายามที่จะรักษาความบริสุทธิ์ซึ่งแนวความคิดทั้งสองนี้มักจะสับสนและนำไปสู่ความสับสน