ก่อนอื่นเรามาแยกความแตกต่างระหว่างการเรียนรู้แนวคิดนามธรรมและการเรียนรู้ตัวอย่างเฉพาะของพวกเขา
คุณจะไม่ไปไกลเกินกว่าจะเพิกเฉยกับตัวอย่างที่เฉพาะเจาะจงทั้งหมดด้วยเหตุผลง่ายๆว่าพวกมันแพร่หลายมากที่สุด ในความเป็นจริง abstractions มีอยู่เป็นส่วนใหญ่เพราะพวกเขารวมสิ่งที่คุณจะทำต่อไปด้วยตัวอย่างเฉพาะ
ในทางตรงกันข้าม abstractions นั้นมีประโยชน์อย่างแน่นอนแต่ก็ไม่จำเป็นทันที คุณสามารถมองข้ามสิ่งที่เป็นนามธรรมได้ทั้งหมดและใช้ประเภทต่าง ๆ โดยตรง คุณจะต้องการที่จะเข้าใจพวกเขาในที่สุด แต่คุณสามารถกลับมาได้ในภายหลัง ที่จริงแล้วฉันเกือบจะรับประกันได้ว่าถ้าคุณทำอย่างนั้นเมื่อคุณกลับไปที่คุณจะตบหน้าผากของคุณและสงสัยว่าทำไมคุณใช้เวลาตลอดเวลาในการทำสิ่งที่ยากแทนการใช้เครื่องมือทั่วไปที่สะดวก
ใช้Maybe a
เป็นตัวอย่าง มันเป็นเพียงประเภทข้อมูล:
data Maybe a = Just a | Nothing
มันคือทั้งหมด แต่การจัดทำเอกสารด้วยตนเอง; มันเป็นค่าเผื่อเลือก ไม่ว่าคุณจะมีอะไรบางอย่าง "แค่" a
หรือคุณไม่มีอะไรเลย สมมติว่าคุณมีฟังก์ชั่นการค้นหาบางประเภทที่กลับMaybe String
ไปเป็นตัวแทนการค้นหาString
ค่าที่อาจไม่มีอยู่ ดังนั้นรูปแบบที่คุณจับคู่กับค่าเพื่อดูว่ามันคืออะไร:
case lookupFunc key of
Just val -> ...
Nothing -> ...
นั่นคือทั้งหมด!
จริงๆไม่มีอะไรที่คุณต้องการ ไม่มีFunctor
s หรือMonad
s หรือสิ่งอื่นใด สิ่งเหล่านั้นแสดงให้เห็นถึงวิธีการทั่วไปในการใช้Maybe a
ค่า ... แต่เป็นเพียงสำนวน "รูปแบบการออกแบบ" สิ่งที่คุณอาจต้องการเรียก
สถานที่แห่งหนึ่งที่คุณไม่สามารถหลีกเลี่ยงได้จริง ๆ อยู่ด้วยIO
แต่นั่นก็เป็นกล่องดำลึกลับอยู่แล้วดังนั้นจึงไม่คุ้มค่าที่จะเข้าใจว่ามันหมายถึงMonad
อะไรหรืออะไรก็ตาม
ในความเป็นจริงนี่เป็นแผ่นโกงสำหรับสิ่งที่คุณจริงๆต้องรู้เกี่ยวกับIO
ตอนนี้:
หากสิ่งที่มีประเภทIO a
นั่นหมายความว่ามันเป็นขั้นตอนที่ทำอะไรบางอย่างและคายa
ค่า
เมื่อคุณมีบล็อกของรหัสโดยใช้do
สัญลักษณ์เขียนสิ่งนี้:
do -- ...
inp <- getLine
-- etc...
... หมายถึงดำเนินการตามขั้นตอนทางด้านขวาของ<-
และกำหนดผลลัพธ์ให้กับชื่อทางด้านซ้าย
โดยที่ถ้าคุณมีสิ่งนี้:
do -- ...
let x = [foo, bar]
-- etc...
... หมายถึงการกำหนดค่าของนิพจน์ธรรมดา (ไม่ใช่โพรซีเดอร์) ทางด้านขวาของ=
ชื่อให้ทางด้านซ้าย
หากคุณใส่บางอย่างที่นั่นโดยไม่กำหนดค่าเช่นนี้
do putStrLn "blah blah, fishcakes"
... มันหมายถึงการดำเนินการตามขั้นตอนและไม่สนใจสิ่งที่จะส่งกลับ บางโพรซีเดอร์มีประเภทIO ()
- ()
ประเภทนั้นเป็นตัวยึดตำแหน่งที่ไม่พูดอะไรเลยดังนั้นหมายความว่าโพรซีเดอร์ทำบางสิ่งและไม่ส่งคืนค่า เรียงจากชอบvoid
ฟังก์ชั่นในภาษาอื่น ๆ
การดำเนินการขั้นตอนเดียวกันมากกว่าหนึ่งครั้งสามารถให้ผลลัพธ์ที่แตกต่างกัน นั่นเป็นแนวคิด นี่คือสาเหตุที่ไม่มีวิธี "ลบ" ออกIO
จากค่าเนื่องจากบางสิ่งในIO
ไม่ใช่ค่ามันเป็นขั้นตอนในการรับค่า
บรรทัดสุดท้ายในdo
บล็อกต้องเป็นกระบวนการธรรมดาที่ไม่มีการกำหนดโดยที่ค่าส่งคืนของกระบวนงานนั้นจะกลายเป็นค่าส่งคืนสำหรับบล็อกทั้งหมด หากคุณต้องการให้ค่าส่งคืนใช้ค่าบางค่าที่กำหนดไว้แล้วreturn
ฟังก์ชั่นจะใช้ค่าธรรมดาและให้ขั้นตอนการไม่ใช้งานที่คืนค่านั้น
นอกเหนือจากนั้นไม่มีอะไรพิเศษเกี่ยวกับIO
; ขั้นตอนเหล่านี้เป็นค่านิยมธรรมดา ๆ และคุณสามารถส่งต่อและรวมเข้าด้วยกันในรูปแบบที่ต่าง มันก็ต่อเมื่อพวกเขาถูกdo
บล็อกในบางกรณีmain
ที่พวกเขาทำอะไร
ดังนั้นในบางสิ่งเช่นโปรแกรมตัวอย่างสำเร็จรูปที่น่าเบื่ออย่างที่สุดนี้:
hello = do putStrLn "What's your name?"
name <- getLine
let msg = "Hi, " ++ name ++ "!"
putStrLn msg
return name
... คุณสามารถอ่านได้เหมือนโปรแกรมที่จำเป็น hello
เรากำลังกำหนดขั้นตอนที่มีชื่อว่า เมื่อดำเนินการขั้นแรกให้ดำเนินการตามขั้นตอนเพื่อพิมพ์ข้อความเพื่อขอชื่อของคุณ ถัดไปจะประมวลผลโพรซีเดอร์ที่อ่านบรรทัดอินพุตและกำหนดผลลัพธ์ให้name
; แล้วก็จะระบุการแสดงออกชื่อmsg
; จากนั้นจะพิมพ์ข้อความ จากนั้นจะส่งคืนชื่อผู้ใช้เป็นผลลัพธ์ของบล็อกทั้งหมด เนื่องจากname
เป็นString
วิธีที่hello
เป็นกระบวนการที่ผลตอบแทนจึงมีประเภทString
IO String
และตอนนี้คุณสามารถดำเนินการตามขั้นตอนนี้ที่อื่น ๆ getLine
เช่นเดียวกับมันรัน
Pfff, monads ใครต้องการ 'em?