รูปแบบการใช้งานที่ไม่บริสุทธิ์ถือว่าถูกปิดหรือไม่?


33

การปิดนั้นถือว่าไม่บริสุทธิ์ในฟังก์ชั่นการเขียนโปรแกรมหรือไม่?

ดูเหมือนว่าเราสามารถหลีกเลี่ยงการปิดโดยการส่งค่าโดยตรงไปยังฟังก์ชั่น ดังนั้นควรหลีกเลี่ยงการปิดที่เป็นไปได้?

หากพวกเขาไม่บริสุทธิ์และฉันถูกต้องในการระบุว่าพวกเขาสามารถหลีกเลี่ยงได้ทำไมภาษาการเขียนโปรแกรมการทำงานจำนวนมากจึงสนับสนุนการปิด?

หนึ่งในเกณฑ์ของฟังก์ชั่น pureคือ "ฟังก์ชั่นจะประเมินค่าผลลัพธ์เดียวกันเสมอซึ่งให้ค่าอาร์กิวเมนต์ที่เหมือนกัน"

สมมติ

f: x -> x + y

f(3)จะไม่ให้ผลลัพธ์เดียวกันเสมอ f(3)ขึ้นอยู่กับค่าของที่ไม่ได้เป็นข้อโต้แย้งของy fดังนั้นจึงfไม่ใช่ฟังก์ชั่นที่บริสุทธิ์

เนื่องจากการปิดทั้งหมดขึ้นอยู่กับค่าที่ไม่ใช่ข้อโต้แย้งมันเป็นไปได้อย่างไรที่การปิดใด ๆ จะบริสุทธิ์ ใช่ในทางทฤษฎีแล้วค่าปิดอาจคงที่ แต่ไม่มีทางรู้ได้ว่าเพียงแค่มองว่าซอร์สโค้ดของฟังก์ชันนั้น

ที่ซึ่งสิ่งนี้ทำให้ฉันเป็นว่าฟังก์ชั่นเดียวกันอาจบริสุทธิ์ในสถานการณ์หนึ่ง แต่ไม่บริสุทธิ์ในอีกสถานการณ์หนึ่ง หนึ่งไม่สามารถเสมอตรวจสอบว่าฟังก์ชั่นบริสุทธิ์หรือไม่โดยการศึกษารหัสแหล่งที่มาของ แต่อาจต้องพิจารณาในบริบทของสภาพแวดล้อม ณ จุดที่มันถูกเรียกก่อนที่ความแตกต่างดังกล่าวจะสามารถทำได้

ฉันกำลังคิดเกี่ยวกับสิ่งนี้ถูกต้องหรือไม่


6
ฉันใช้การปิดตลอดเวลาใน Haskell และ Haskell นั้นบริสุทธิ์อย่างที่ได้รับ
Thomas Eding

5
ในภาษาที่ใช้งานyได้จริงไม่สามารถเปลี่ยนแปลงได้ดังนั้นผลลัพธ์ของf(3)จะเหมือนเดิมเสมอ
Lily Chung

4
yเป็นส่วนหนึ่งของคำนิยามของfแม้ว่ามันจะไม่ได้ทำเครื่องหมายเป็นอินพุตอย่างชัดเจนf- แต่ก็ยังเป็นกรณีที่fกำหนดไว้ในแง่ของy(เราสามารถแสดงฟังก์ชัน f_y เพื่อให้การพึ่งพาyชัดเจน) และการเปลี่ยนแปลงจึงyให้ฟังก์ชันที่แตกต่างกัน . ฟังก์ชั่นเฉพาะที่f_yกำหนดไว้สำหรับเฉพาะyนั้นบริสุทธิ์มาก (ตัวอย่างเช่นทั้งสองฟังก์ชั่นf: x -> x + 3และf: x -> x + 5เป็นฟังก์ชั่นที่แตกต่างกันและบริสุทธิ์ทั้ง ๆ ที่แม้ว่าเราจะใช้ตัวอักษรเดียวกันเพื่อแสดงถึงพวกเขา)
ShreevatsaR

คำตอบ:


26

ความบริสุทธิ์สามารถวัดได้โดยสองสิ่ง:

  1. ฟังก์ชั่นส่งคืนเอาต์พุตเดียวกันเสมอหรือไม่โดยรับอินพุตเดียวกัน คือมันคือreferential โปร่งใส?
  2. ฟังก์ชั่นนี้แก้ไขสิ่งภายนอกตัวเองหรือไม่เช่นมีผลข้างเคียงหรือไม่?

หากคำตอบของ 1 คือใช่และคำตอบของ 2 คือไม่แสดงว่าฟังก์ชั่นนั้นบริสุทธิ์ การปิดทำให้ฟังก์ชั่นไม่บริสุทธิ์หากคุณปรับเปลี่ยนตัวแปรปิดเกิน


ไม่ใช่รายการแรกที่กำหนด? หรือว่าเป็นส่วนหนึ่งของความบริสุทธิ์ด้วย? ฉันไม่คุ้นเคยกับแนวคิดของ "ความบริสุทธิ์" ในบริบทของการเขียนโปรแกรม

4
@JimmyHoffa: ไม่จำเป็น คุณสามารถใส่เอาท์พุทของตัวจับเวลาฮาร์ดแวร์ในฟังก์ชั่นและไม่มีการแก้ไขอะไรนอกฟังก์ชั่น
Robert Harvey

1
@RobertHarvey นี่คือทั้งหมดที่เกี่ยวกับวิธีการที่เรากำหนดอินพุตให้กับฟังก์ชั่นหรือไม่? คำพูดของฉันจากวิกิพีเดียมุ่งเน้นไปที่ฟังก์ชั่นการโต้แย้งในขณะที่คุณยังพิจารณาตัวแปรปิด (s) เป็นปัจจัยการผลิต
user2179977

8
@ user2179977: นอกจากว่าพวกเขาไม่สามารถเปลี่ยนแปลงได้คุณไม่ควรพิจารณาตัวแปรปิดเป็นข้อมูลเพิ่มเติมสำหรับฟังก์ชั่น แต่คุณควรพิจารณาว่าการปิดตัวเองเป็นฟังก์ชั่นและเป็นฟังก์ชั่นที่แตกต่างกันเมื่อมันปิดมากกว่าค่าyอื่น ดังนั้นสำหรับตัวอย่างเช่นเรากำหนดฟังก์ชั่นgดังกล่าวว่าเป็นตัวเองฟังก์ชั่นg(y) x -> x + yจากนั้นgเป็นฟังก์ชันของจำนวนเต็มที่ส่งคืนฟังก์ชันg(3)เป็นฟังก์ชันของจำนวนเต็มที่ส่งคืนจำนวนเต็มและg(2)เป็นฟังก์ชันที่แตกต่างของจำนวนเต็มที่คืนค่าจำนวนเต็ม ฟังก์ชั่นทั้งสามนั้นบริสุทธิ์
Steve Jessop

1
@ Darkhogg: ใช่ ดูการปรับปรุงของฉัน
Robert Harvey

10

การปรากฏตัวของแลมบ์ดาแคลคูลัสซึ่งเป็นรูปแบบการเขียนโปรแกรมที่ใช้งานได้จริงที่สุดดังนั้นฉันจะไม่เรียกมันว่า "ไม่บริสุทธิ์" ...

การปิดไม่ได้ "ไม่บริสุทธิ์" เพราะฟังก์ชั่นในภาษาที่ใช้งานได้เป็นพลเมืองชั้นหนึ่ง - นั่นหมายความว่าพวกเขาสามารถถือว่าเป็นค่า

ลองจินตนาการถึงสิ่งนี้ (pseudocode):

foo(x) {
    let y = x + 1
    ...
}

yเป็นค่า ค่าขึ้นอยู่กับxแต่xไม่สามารถเปลี่ยนแปลงได้ดังนั้นyค่าของมันจึงไม่เปลี่ยนรูป เราสามารถเรียกfooหลายครั้งด้วยข้อโต้แย้งที่แตกต่างกันซึ่งจะสร้างys ที่แตกต่างกันแต่yทั้งหมดนั้นอยู่ในขอบเขตที่แตกต่างกันและขึ้นอยู่กับxs ที่แตกต่างกันดังนั้นความบริสุทธิ์ยังคงไม่บุบสลาย

ตอนนี้ขอเปลี่ยน:

bar(x) {
    let y(z) = x + z
    ....
}

ที่นี่เรากำลังใช้การปิด (เรากำลังปิด x) แต่มันก็เหมือนกับในfoo- การเรียกที่barแตกต่างกับอาร์กิวเมนต์ที่แตกต่างกันสร้างค่าที่แตกต่างกันของy(จำไว้ว่า - ฟังก์ชั่นเป็นค่า) ซึ่งไม่เปลี่ยนรูปทั้งหมด

นอกจากนี้โปรดทราบว่าการปิดมีผลคล้ายกับการแกง:

adder(a)(b) {
    return a + b
}
baz(x) {
    let y = adder(x)
    ...
}

bazไม่ได้แตกต่างกันจริงๆกว่าbar- ทั้งเราสร้างค่าฟังก์ชั่นที่มีชื่อว่าผลตอบแทนของมันอาร์กิวเมนต์บวกy xในความเป็นจริงในแลมบ์ดาแคลคูลัสคุณใช้การปิดเพื่อสร้างฟังก์ชั่นที่มีอาร์กิวเมนต์หลายตัว - และมันก็ยังไม่บริสุทธิ์


9

คนอื่น ๆ ได้ครอบคลุมคำถามทั่วไปอย่างดีในคำตอบของพวกเขาดังนั้นฉันจะดูเฉพาะการล้างความสับสนที่คุณส่งสัญญาณในการแก้ไขของคุณ

การปิดไม่ได้กลายเป็นอินพุทของฟังก์ชั่น แต่มันจะเข้าไปในส่วนของฟังก์ชั่น จะเป็นรูปธรรมมากขึ้นฟังก์ชั่นหมายถึงค่าในขอบเขตด้านนอกในร่างกายของมัน

คุณอยู่ภายใต้การแสดงผลที่ทำให้ฟังก์ชันไม่บริสุทธิ์ นั่นไม่ใช่กรณีทั่วไป ในการเขียนโปรแกรมการทำงานค่าจะไม่เปลี่ยนรูปมากที่สุดของเวลา ที่ใช้กับค่าปิดเช่นกัน

สมมติว่าคุณมีรหัสเช่นนี้:

let make y =
    fun x -> x + y

โทรmake 3และmake 4จะทำให้คุณทั้งสองฟังก์ชั่นที่มีการปิดมากกว่าmake's yอาร์กิวเมนต์ หนึ่งของพวกเขาจะกลับมาอีกx + 3 x + 4อย่างไรก็ตามมันมีฟังก์ชั่นที่แตกต่างกันสองอย่างและทั้งสองอย่างนั้นบริสุทธิ์ พวกเขาถูกสร้างขึ้นโดยใช้makeฟังก์ชั่นเดียวกันแต่มัน

สังเกตเวลาส่วนใหญ่สองสามย่อหน้าย้อนหลัง

  1. ใน Haskell ซึ่งบริสุทธิ์คุณสามารถปิดเฉพาะค่าที่ไม่เปลี่ยนรูปเท่านั้น ไม่มีสถานะที่ไม่แน่นอนที่จะปิด คุณแน่ใจว่าจะได้รับฟังก์ชั่นที่บริสุทธิ์อย่างนั้น
  2. ในภาษาที่ไม่บริสุทธิ์เช่น F # คุณสามารถปิดเซลล์อ้างอิงและประเภทการอ้างอิงและรับฟังก์ชั่นที่ไม่บริสุทธิ์ได้ คุณมีสิทธิ์ที่คุณจะต้องติดตามขอบเขตที่ฟังก์ชั่นนั้นถูกกำหนดให้รู้ว่ามันบริสุทธิ์หรือไม่ คุณสามารถบอกได้อย่างง่ายดายว่าค่านั้นไม่แน่นอนในภาษาเหล่านั้นหรือไม่ดังนั้นจึงไม่ใช่ปัญหา
  3. ในภาษา OOP ที่รองรับการปิดเช่น C # และ JavaScript สถานการณ์คล้ายกับภาษาที่ไม่บริสุทธิ์ แต่การติดตามขอบเขตด้านนอกจะยุ่งยากมากขึ้นเนื่องจากตัวแปรไม่แน่นอนโดยค่าเริ่มต้น

โปรดทราบว่าสำหรับ 2 และ 3 ภาษาเหล่านั้นไม่มีการรับประกันใด ๆ เกี่ยวกับความบริสุทธิ์ ความไม่บริสุทธิ์ไม่มีคุณสมบัติของการปิด แต่ของภาษาเอง การปิดไม่เปลี่ยนภาพมากนัก


1
คุณสามารถปิดค่าที่ไม่แน่นอนใน Haskell ได้อย่างแน่นอน แต่สิ่งนี้จะถูกใส่คำอธิบายประกอบด้วย IO monad
Daniel Gratzer

1
@jozefg ไม่คุณปิดIO Aค่าที่ไม่เปลี่ยนรูปแบบและประเภทการปิดของคุณคือIO (B -> C)หรือ somesuch รักษาความบริสุทธิ์
Caleth

5

โดยปกติฉันจะขอให้คุณอธิบายความหมายของคำว่า "ไม่บริสุทธิ์" แต่ในกรณีนี้มันไม่สำคัญ สมมติว่าคุณตัดกันคำที่ใช้งานได้อย่างหมดจดคำตอบคือ "ไม่" เพราะไม่มีอะไรเกี่ยวกับการปิดที่ทำลายโดยเนื้อแท้ หากภาษาของคุณทำงานได้อย่างหมดจดโดยไม่ต้องปิดมันก็ยังคงทำงานได้อย่างหมดจดด้วยการปิด หากคุณหมายถึง "ไม่ทำงาน" แต่คำตอบนั้นยังคงเป็น "ไม่" การปิดช่วยอำนวยความสะดวกในการสร้างฟังก์ชั่น

ดูเหมือนว่าเราสามารถหลีกเลี่ยงการปิดโดยการส่งข้อมูลโดยตรงไปยังฟังก์ชั่น

ใช่ แต่ฟังก์ชั่นของคุณจะมีพารามิเตอร์อีกหนึ่งพารามิเตอร์และนั่นจะเปลี่ยนประเภทของมัน การปิดช่วยให้คุณสร้างฟังก์ชั่นตามตัวแปรโดยไม่ต้องเพิ่มพารามิเตอร์ สิ่งนี้มีประโยชน์เมื่อคุณพูดฟังก์ชั่นที่รับ 2 อาร์กิวเมนต์และคุณต้องการสร้างเวอร์ชันที่ใช้ 1 อาร์กิวเมนต์เท่านั้น

แก้ไข:เกี่ยวกับการแก้ไข / ตัวอย่างของคุณเอง ...

สมมติ

f: x -> x + y

f (3) จะไม่ให้ผลลัพธ์เดียวกันเสมอไป f (3) ขึ้นอยู่กับค่าของ y ซึ่งไม่ใช่อาร์กิวเมนต์ของ f ดังนั้น f จึงไม่ใช่ฟังก์ชั่นที่บริสุทธิ์

ขึ้นอยู่กับการเลือกคำผิดที่นี่ การอ้างถึงบทความ Wikipedia เดียวกันกับที่คุณทำ:

ในการเขียนโปรแกรมคอมพิวเตอร์ฟังก์ชั่นอาจอธิบายได้ว่าเป็นฟังก์ชั่นที่บริสุทธิ์ถ้าทั้งสองข้อความเหล่านี้เกี่ยวกับฟังก์ชั่นระงับ:

  1. ฟังก์ชันจะประเมินค่าผลลัพธ์เดียวกันเสมอซึ่งให้ค่าอาร์กิวเมนต์ที่เหมือนกันเสมอ ค่าผลลัพธ์ของฟังก์ชันไม่สามารถขึ้นอยู่กับข้อมูลหรือสถานะใด ๆ ที่ซ่อนอยู่ซึ่งอาจเปลี่ยนแปลงได้เมื่อการดำเนินการของโปรแกรมดำเนินไปหรือระหว่างการดำเนินการที่แตกต่างกันของโปรแกรมและไม่สามารถขึ้นอยู่กับอินพุตภายนอกจากอุปกรณ์ I / O
  2. การประเมินผลลัพธ์ไม่ได้ทำให้เกิดผลข้างเคียงหรือเอาท์พุทที่สังเกตเห็นได้ทางความหมายเช่นการกลายพันธุ์ของวัตถุที่ไม่แน่นอนหรือเอาท์พุทไปยังอุปกรณ์ I / O

สมมติว่าyไม่เปลี่ยนรูป (ซึ่งโดยปกติจะเป็นในภาษาที่ใช้งานได้), เงื่อนไข 1 เป็นที่พอใจ: สำหรับทุกค่าของx, ค่าของf(x)จะไม่เปลี่ยนแปลง นี่ควรจะชัดเจนจากความจริงที่ว่าyไม่แตกต่างจากค่าคงที่และx + 3บริสุทธิ์ เห็นได้ชัดว่าไม่มีการกลายพันธุ์หรือ I / O เกิดขึ้น


3

เร็วมาก: การทดแทนคือ "โปร่งใสแบบอ้างอิงได้" ถ้า "การทดแทนเช่นนำไปสู่การชอบ" และฟังก์ชั่นคือ "บริสุทธิ์" หากเอฟเฟกต์ทั้งหมดมีอยู่ในค่าส่งคืน ทั้งสองอย่างนั้นสามารถสร้างได้อย่างแม่นยำ แต่เป็นสิ่งสำคัญที่จะต้องทราบว่าพวกเขาไม่เหมือนกัน

ตอนนี้เรามาพูดเกี่ยวกับการปิด

น่าเบื่อ (ส่วนใหญ่บริสุทธิ์) "ปิด"

การปิดเกิดขึ้นเนื่องจากเมื่อเราประเมินคำแลมบ์ดาเราจะตีความตัวแปรที่ถูกผูกไว้ว่าเป็นการค้นหาสภาพแวดล้อม ดังนั้นเมื่อเรากลับคำแลมบ์ดาซึ่งเป็นผลมาจากการประเมินตัวแปรภายในจะมี "ปิด" ค่าที่พวกเขาใช้เมื่อมันถูกกำหนด

ในแคลคูลัสแลมบ์ดาธรรมดานี่เป็นเรื่องเล็กน้อยและความคิดทั้งหมดก็หายไป เพื่อแสดงให้เห็นว่านี่เป็นล่ามแคลคูลัสแลมบ์ดาที่ค่อนข้างเบา:

-- 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ที่ได้รับการบันทึกโดยการลากที่มีAbsVar

ตอนนี้อีกครั้งในแง่ 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 มาก แต่แจ้งให้ทราบว่าผลข้างเคียงที่ไม่ได้เกิดขึ้นจากการจับภาพชื่อหรือปิด แต่แทนที่จะจับของมือถือที่ไม่แน่นอนและการดำเนินงานด้านผลต่อมันเหมือนและgetput

ในที่สุดฉันคิดว่านี่เป็นคำตอบสุดท้ายสำหรับคำถามของคุณ: ความบริสุทธิ์ไม่ได้รับผลกระทบจากการจับตัวแปร (ทางคณิตศาสตร์) แต่แทนที่ด้วยการดำเนินการด้านผลกระทบที่ดำเนินการกับช่องที่ไม่แน่นอนที่ตั้งชื่อโดยตัวแปรที่บันทึก

เฉพาะในภาษาที่ไม่ได้พยายามที่จะใกล้เคียงกับ LC หรือไม่พยายามที่จะรักษาความบริสุทธิ์ซึ่งแนวความคิดทั้งสองนี้มักจะสับสนและนำไปสู่ความสับสน


1

ไม่การปิดไม่ทำให้ฟังก์ชั่นไม่บริสุทธิ์ตราบใดที่ค่าปิดเป็นค่าคงที่ (ไม่เปลี่ยนแปลงโดยการปิดหรือรหัสอื่น) ซึ่งเป็นกรณีปกติในการเขียนโปรแกรมเชิงฟังก์ชัน

โปรดทราบว่าในขณะที่คุณสามารถส่งผ่านค่าเป็นอาร์กิวเมนต์แทนได้เสมอโดยปกติคุณจะไม่สามารถทำได้โดยไม่ต้องลำบากมาก ตัวอย่างเช่น (coffeescript):

closedValue = 42
return (arg) -> console.log "#{closedValue} #{arg}"

ตามคำแนะนำของคุณคุณสามารถคืนสินค้าได้:

return (arg, closedValue) -> console.log "#{closedValue} #{arg}"

ฟังก์ชั่นนี้ไม่ได้ถูกเรียกณ จุดนี้เพียงแค่กำหนดไว้ดังนั้นคุณต้องหาวิธีที่จะส่งค่าที่คุณต้องการclosedValueไปยังจุดที่ฟังก์ชั่นนั้นถูกเรียก ที่ดีที่สุดนี้จะสร้างการแต่งงานกันมาก ที่แย่ที่สุดคุณไม่สามารถควบคุมรหัสที่จุดโทรดังนั้นจึงเป็นไปไม่ได้อย่างมีประสิทธิภาพ

ไลบรารีเหตุการณ์ในภาษาที่ไม่รองรับการปิดมักจะให้วิธีการอื่นในการส่งผ่านข้อมูลโดยพลการกลับไปที่การโทรกลับ แต่มันก็ไม่สวยและสร้างความซับซ้อนมากมายทั้งสำหรับผู้ดูแลห้องสมุดและผู้ใช้ห้องสมุด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.