สำหรับวัตถุประสงค์ของคำตอบนี้ฉันได้นิยาม "ภาษาที่ใช้งานได้อย่างแท้จริง" เพื่อหมายถึงภาษาที่ใช้งานได้ซึ่งฟังก์ชั่นนั้นมีความโปร่งใสในระดับอ้างอิงเช่นการเรียกใช้ฟังก์ชันเดียวกันหลายครั้งด้วยอาร์กิวเมนต์ที่เหมือนกัน นี่คือฉันเชื่อว่าคำจำกัดความปกติของภาษาที่ใช้งานได้อย่างหมดจด
ภาษาการเขียนโปรแกรมที่ใช้งานได้จริงไม่อนุญาตให้มีผลข้างเคียง (และมีประโยชน์น้อยในทางปฏิบัติเพราะโปรแกรมที่มีประโยชน์ใด ๆ มีผลข้างเคียงเช่นเมื่อมันโต้ตอบกับโลกภายนอก)
วิธีที่ง่ายที่สุดในการบรรลุความโปร่งใสในการอ้างอิงคือการไม่อนุญาตให้มีผลข้างเคียงและมีภาษาที่แน่นอนในกรณีนี้ (ส่วนใหญ่เป็นโดเมนเฉพาะ) อย่างไรก็ตามมันไม่ใช่วิธีเดียวและเป็นภาษาที่ใช้งานได้ทั่วๆไป (Haskell, Clean, ... ) ทำให้เกิดผลข้างเคียง
นอกจากนี้ยังบอกว่าภาษาการเขียนโปรแกรมที่ไม่มีผลข้างเคียงคือการใช้งานในทางปฏิบัติน้อยมากฉันคิดว่าไม่ยุติธรรม - ไม่ใช่สำหรับภาษาเฉพาะโดเมน แต่สำหรับภาษาที่ใช้โดยทั่วไปฉันคิดว่าภาษาจะมีประโยชน์มากหากไม่มีผลข้างเคียง . อาจไม่ใช่สำหรับแอปพลิเคชั่นคอนโซล แต่ฉันคิดว่าแอปพลิเคชัน GUI สามารถนำไปใช้งานได้อย่างดีโดยไม่มีผลข้างเคียงในกระบวนทัศน์การทำงานแบบโต้ตอบ
เกี่ยวกับจุดที่ 1 คุณสามารถโต้ตอบกับสภาพแวดล้อมในภาษาที่ใช้งานได้อย่างหมดจด แต่คุณต้องทำเครื่องหมายรหัส (ฟังก์ชั่น) อย่างชัดเจนที่แนะนำพวกเขา (เช่นใน Haskell ด้วยวิธีการแบบ monadic)
นั่นเป็นวิธีที่ง่ายกว่า เพียงแค่มีระบบที่จำเป็นต้องทำเครื่องหมายฟังก์ชันที่มีผลข้างเคียง (คล้ายกับความถูกต้องของ const ใน C ++ แต่ด้วยผลข้างเคียงทั่วไป) ไม่เพียงพอที่จะทำให้มั่นใจถึงความโปร่งใสในการอ้างอิง คุณต้องแน่ใจว่าโปรแกรมไม่สามารถเรียกใช้ฟังก์ชันได้หลายครั้งด้วยอาร์กิวเมนต์เดียวกันและรับผลลัพธ์ที่แตกต่างกัน คุณสามารถทำได้โดยการทำสิ่งต่าง ๆ เช่นreadLine
เป็นสิ่งที่ไม่ใช่ฟังก์ชั่น (นั่นคือสิ่งที่ Haskell ทำกับ IO monad) หรือคุณอาจทำให้เป็นไปไม่ได้ที่จะเรียกใช้ฟังก์ชันที่มีผลกระทบด้านข้างหลายครั้งด้วยอาร์กิวเมนต์เดียวกัน (นั่นคือสิ่งที่ Clean ทำ) ในกรณีหลังคอมไพเลอร์จะตรวจสอบให้แน่ใจว่าทุกครั้งที่คุณเรียกฟังก์ชั่นผลกระทบด้านข้างคุณทำเช่นนั้นด้วยอาร์กิวเมนต์ใหม่และมันจะปฏิเสธโปรแกรมใด ๆ ที่คุณส่งอาร์กิวเมนต์เดียวกันไปยังฟังก์ชันผลกระทบข้างเคียงสองครั้ง
ภาษาการเขียนโปรแกรมที่ใช้งานได้จริงไม่อนุญาตให้เขียนโปรแกรมที่รักษาสถานะ (ซึ่งทำให้การเขียนโปรแกรมเป็นเรื่องที่น่าอึดอัดใจมากเพราะในหลาย ๆ แอปพลิเคชันที่คุณต้องการสถานะ)
อีกครั้งภาษาที่ใช้งานได้จริงอาจไม่อนุญาตให้มีสถานะที่ไม่แน่นอน แต่ก็เป็นไปได้ที่จะบริสุทธิ์และยังคงมีสถานะที่ไม่แน่นอนหากคุณใช้งานในลักษณะเดียวกับที่ฉันอธิบายด้วยผลข้างเคียง สภาพที่ไม่แน่นอนจริง ๆ เป็นเพียงผลข้างเคียงรูปแบบอื่น
ที่กล่าวว่าภาษาการเขียนโปรแกรมการทำงานแน่นอนทำให้รัฐไม่แน่นอนเปลี่ยนแปลง - บริสุทธิ์โดยเฉพาะอย่างยิ่งดังนั้น และฉันไม่คิดว่ามันจะทำให้การเขียนโปรแกรมงุ่มง่าม - ค่อนข้างตรงกันข้าม บางครั้ง (แต่ไม่ใช่ทั้งหมดที่มักจะ) สถานะที่เปลี่ยนแปลงไม่ได้ไม่สามารถหลีกเลี่ยงได้โดยไม่สูญเสียประสิทธิภาพหรือความชัดเจน (ซึ่งเป็นสาเหตุที่ภาษาเช่น Haskell มีสิ่งอำนวยความสะดวกสำหรับสถานะที่ไม่แน่นอน) แต่ส่วนใหญ่สามารถทำได้
หากพวกเขาเข้าใจผิดพวกเขามาอย่างไร
ฉันคิดว่าหลายคนอ่าน "ฟังก์ชั่นจะต้องให้ผลลัพธ์เหมือนกันเมื่อถูกเรียกด้วยอาร์กิวเมนต์เดียวกัน" และสรุปได้ว่ามันเป็นไปไม่ได้ที่จะใช้บางอย่างเช่นreadLine
รหัสหรือรหัสที่รักษาสถานะที่ไม่แน่นอน ดังนั้นพวกเขาจึงไม่ได้ตระหนักถึง "กลโกง" ที่ภาษาที่ใช้งานได้จริงสามารถใช้เพื่อแนะนำสิ่งเหล่านี้โดยไม่ทำลายความโปร่งใสในการอ้างอิง
นอกจากนี้สถานะที่เปลี่ยนแปลงไม่ได้นั้นเป็นอุปสรรคอย่างมากในภาษาที่ใช้งานได้ดังนั้นจึงไม่เป็นการเผ่นเลยที่จะคิดว่ามันไม่ได้รับอนุญาตให้ใช้ในภาษาที่ทำงานได้อย่างแท้จริง
คุณสามารถเขียนข้อมูลโค้ด (อาจเล็ก) ที่แสดงวิธี Haskell สำนวนเพื่อ (1) ใช้ผลข้างเคียงและ (2) ใช้การคำนวณกับรัฐได้หรือไม่?
นี่คือแอปพลิเคชันใน Pseudo-Haskell ที่ขอชื่อผู้ใช้และทักทายเขา หลอก Haskell เป็นภาษาที่ฉันเพิ่งคิดค้นซึ่งมีระบบ IO Haskell แต่ใช้ไวยากรณ์ธรรมดาชื่อฟังก์ชั่นที่สื่อความหมายมากขึ้นและไม่เคยมีใครdo
-notation (เป็นที่จะหันเหความสนใจจากเพียงวิธีการที่ตรง IO monad งาน):
greet(name) = print("Hello, " ++ name ++ "!")
main = composeMonad(readLine, greet)
เงื่อนงำที่นี่คือreadLine
ค่าประเภทIO<String>
และcomposeMonad
เป็นฟังก์ชันที่รับอาร์กิวเมนต์ชนิดIO<T>
(สำหรับบางประเภทT
) และอาร์กิวเมนต์อื่นที่เป็นฟังก์ชันที่รับอาร์กิวเมนต์ชนิดT
และคืนค่าประเภทIO<U>
(สำหรับบางประเภทU
) เป็นฟังก์ชั่นที่ใช้เวลาสตริงและส่งกลับค่าชนิดprint
IO<void>
ค่าของประเภทIO<A>
เป็นค่าที่ "ถอดรหัส" A
การดำเนินการให้ที่ก่อให้เกิดค่าของชนิด composeMonad(m, f)
ผลิตใหม่IO
ค่าที่เข้ารหัสการกระทำของm
ตามมาด้วยการกระทำของf(x)
ซึ่งเป็นค่าที่ผลิตโดยการดำเนินการการกระทำของx
m
รัฐที่ไม่แน่นอนจะมีลักษณะเช่นนี้:
counter = mutableVariable(0)
increaseCounter(cnt) =
setIncreasedValue(oldValue) = setValue(cnt, oldValue + 1)
composeMonad(getValue(cnt), setIncreasedValue)
printCounter(cnt) = composeMonad( getValue(cnt), print )
main = composeVoidMonad( increaseCounter(counter), printCounter(counter) )
นี่mutableVariable
คือฟังก์ชั่นที่ใช้ค่าของชนิดใด ๆและผลิตT
MutableVariable<T>
ฟังก์ชั่นgetValue
ใช้MutableVariable
และส่งกลับค่าIO<T>
ที่สร้างมูลค่าปัจจุบัน setValue
รับ a MutableVariable<T>
และ a T
และคืนค่าIO<void>
ที่กำหนดค่า composeVoidMonad
เหมือนกับcomposeMonad
ยกเว้นว่าอาร์กิวเมนต์แรกคือสิ่งIO
ที่ไม่สร้างมูลค่าที่สมเหตุสมผลและอาร์กิวเมนต์ที่สองคือ monad อื่นไม่ใช่ฟังก์ชันที่ส่งคืน monad
ในแฮสเค็ลล์มีน้ำตาลประโยคที่ทำให้ความเจ็บปวดเจ็บปวดน้อยลง แต่ก็ยังเห็นได้ชัดว่าสถานะที่เปลี่ยนแปลงไม่ได้นั้นเป็นสิ่งที่ภาษาไม่ต้องการให้คุณทำ