Referential transparent หรือที่อ้างอิงถึงฟังก์ชั่นบ่งชี้ว่าคุณสามารถกำหนดผลลัพธ์ของการใช้ฟังก์ชันนั้นได้โดยดูที่ค่าของอาร์กิวเมนต์ คุณสามารถเขียนฟังก์ชันโปร่งใสแบบอ้างอิงได้ในภาษาการเขียนโปรแกรมเช่น Python, Scheme, Pascal, C
ในอีกหลายภาษาคุณสามารถเขียนฟังก์ชั่นที่ไม่อ้างอิงแบบโปร่งใสได้ ตัวอย่างเช่นฟังก์ชัน Python นี้:
counter = 0
def foo(x):
global counter
counter += 1
return x + counter
ไม่อ้างอิงโปร่งใสในความเป็นจริงโทร
foo(x) + foo(x)
และ
2 * foo(x)
x
จะผลิตค่าที่แตกต่างกันสำหรับการโต้แย้งใด ๆ เหตุผลนี้เป็นเพราะฟังก์ชั่นใช้และแก้ไขตัวแปรทั่วโลกดังนั้นผลลัพธ์ของการเรียกแต่ละครั้งขึ้นอยู่กับสถานะการเปลี่ยนแปลงนี้และไม่เพียง แต่ในการโต้แย้งฟังก์ชั่น
Haskell เป็นภาษาที่ใช้งานได้จริงโดยแยกการประเมินนิพจน์ที่ใช้ฟังก์ชั่น pureอย่างชัดเจนและโปร่งใสซึ่งอ้างอิงจากการดำเนินการ (การประมวลผลของค่าพิเศษ) ซึ่งไม่โปร่งใสอ้างอิงเช่นการดำเนินการเดียวกันสามารถมีได้ทุกครั้ง ผลลัพธ์ที่แตกต่าง
ดังนั้นสำหรับฟังก์ชั่นใด ๆ ของ Haskell
f :: Int -> Int
และจำนวนเต็มใด ๆx
มันเป็นความจริงเสมอ
2 * (f x) == (f x) + (f x)
ตัวอย่างของการกระทำคือผลลัพธ์ของฟังก์ชันไลบรารีgetLine
:
getLine :: IO String
อันเป็นผลมาจากการประเมินผลการแสดงออกของฟังก์ชั่นนี้ (ที่จริงคงที่) IO String
ครั้งแรกของทุกผลิตค่าบริสุทธิ์จากประเภท ค่าของประเภทนี้คือค่าที่เหมือนกัน: คุณสามารถผ่านไปมาวางไว้ในโครงสร้างข้อมูลเขียนโดยใช้ฟังก์ชั่นพิเศษและอื่น ๆ ตัวอย่างเช่นคุณสามารถสร้างรายการของการกระทำเช่นนั้น:
[getLine, getLine] :: [IO String]
การดำเนินการเป็นสิ่งพิเศษที่คุณสามารถบอกให้รันไทม์ Haskell เพื่อดำเนินการได้โดยการเขียน:
main = <some action>
ในกรณีนี้เมื่อโปรแกรม Haskell ของคุณเริ่มทำงานรันไทม์จะดำเนินการผ่านการดำเนินการที่เชื่อมโยงmain
และดำเนินการซึ่งอาจทำให้เกิดผลข้างเคียง ดังนั้นการดำเนินการแอคชั่นจึงไม่โปร่งใสเนื่องจากการดำเนินการแอ็คชันเดียวกันสองครั้งสามารถสร้างผลลัพธ์ที่แตกต่างกันขึ้นอยู่กับสิ่งที่รันไทม์ได้รับเป็นอินพุต
ต้องขอบคุณระบบประเภทของ Haskell จึงไม่สามารถใช้การกระทำในบริบทที่คาดว่าจะมีประเภทอื่นและในทางกลับกัน ดังนั้นหากคุณต้องการค้นหาความยาวของสตริงคุณสามารถใช้length
ฟังก์ชัน:
length "Hello"
จะส่งคืน 5. แต่ถ้าคุณต้องการค้นหาความยาวของสตริงที่อ่านจากเทอร์มินัลคุณไม่สามารถเขียนได้
length (getLine)
เนื่องจากคุณได้รับข้อผิดพลาดประเภท: length
คาดว่าอินพุตของรายการประเภท (และสตริงคือแท้จริงรายการ) แต่getLine
เป็นค่าประเภทIO String
(การกระทำ) วิธีนี้จะช่วยให้มั่นใจระบบการพิมพ์ที่มีมูลค่าการกระทำเช่นgetLine
(ที่มีการดำเนินการจะดำเนินการนอกภาษาหลักและซึ่งอาจจะไม่ใช่ referentially โปร่งใส) Int
ไม่สามารถที่ซ่อนอยู่ภายในค่าที่ไม่ใช่การกระทำของประเภท
แก้ไข
เพื่อตอบคำถาม exizt ต่อไปนี้เป็นโปรแกรม Haskell ขนาดเล็กที่อ่านบรรทัดจากคอนโซลและพิมพ์ความยาว
main :: IO () -- The main program is an action of type IO ()
main = do
line <- getLine
putStrLn (show (length line))
การดำเนินการหลักประกอบด้วยสองการโต้ตอบที่ดำเนินการตามลำดับ:
getline
ประเภทIO String
,
- ที่สองคือการสร้างโดยการประเมินฟังก์ชั่น
putStrLn
ของประเภทString -> IO ()
บนการโต้แย้ง
แม่นยำยิ่งกว่าการกระทำที่สองถูกสร้างขึ้นโดย
- ผูกพัน
line
กับค่าที่อ่านโดยการกระทำแรก
- การประเมินฟังก์ชั่นแท้
length
(คำนวณความยาวเป็นจำนวนเต็ม) แล้วshow
(เปลี่ยนจำนวนเต็มเป็นสตริง)
- การสร้างการดำเนินการโดยใช้ฟังก์ชั่นเพื่อผลมาจากการ
putStrLn
show
ณ จุดนี้การดำเนินการที่สองสามารถดำเนินการได้ หากคุณพิมพ์ "Hello" ระบบจะพิมพ์ "5"
โปรดทราบว่าหากคุณได้รับค่าจากการกระทำโดยใช้<-
สัญกรณ์คุณสามารถใช้ค่านั้นในการดำเนินการอื่นเช่นคุณไม่สามารถเขียน:
main = do
line <- getLine
show (length line) -- Error:
-- Expected type: IO ()
-- Actual type: String
เพราะshow (length line)
มีประเภทString
ในขณะที่เครื่องหมายต้องกำหนดว่าการกระทำ ( getLine
ประเภทIO String
) ตามด้วยการกระทำอื่น (เช่นputStrLn (show (length line))
ประเภทIO ()
)
แก้ไข 2
คำจำกัดความความโปร่งใสในการอ้างอิงของJörg W Mittag นั้นกว้างกว่าของฉันมาก (ฉันตอบคำตอบของเขาแล้ว) ฉันได้ใช้คำจำกัดความที่ถูก จำกัด เพราะตัวอย่างในคำถามมุ่งเน้นไปที่ค่าส่งคืนของฟังก์ชั่นและฉันต้องการแสดงแง่มุมนี้ อย่างไรก็ตาม RT โดยทั่วไปหมายถึงความหมายของโปรแกรมทั้งหมดรวมถึงการเปลี่ยนแปลงสถานะโลกและการโต้ตอบกับสภาพแวดล้อม (IO) ที่เกิดจากการประเมินการแสดงออก ดังนั้นสำหรับคำจำกัดความทั่วไปที่ถูกต้องคุณควรอ้างถึงคำตอบนั้น