จำนวนการดู # 1 และ # 2 ไม่ถูกต้อง
- ประเภทข้อมูลใด ๆ
* -> *
สามารถทำงานเป็นป้ายกำกับพระได้มากกว่านั้น
- (ยกเว้นของ
IO
monad) การคำนวณภายใน monad นั้นไม่บริสุทธิ์ มันเป็นเพียงการคำนวณที่เราเห็นว่ามีผลข้างเคียง แต่บริสุทธิ์
ความเข้าใจผิดทั้งสองนี้มาจากการมุ่งเน้นไปที่IO
monad ซึ่งจริงๆแล้วค่อนข้างพิเศษ
ฉันจะพยายามอธิบายอย่างละเอียดในข้อที่ 3 เล็กน้อยโดยไม่ต้องคำนึงถึงทฤษฎีหมวดหมู่หากเป็นไปได้
การคำนวณมาตรฐาน
f :: a -> b
การคำนวณทั้งหมดในภาษาเขียนโปรแกรมการทำงานสามารถดูเป็นฟังก์ชั่นที่มีประเภทแหล่งที่มาและประเภทเป้าหมาย: หากฟังก์ชั่นมีมากกว่าหนึ่งอาร์กิวเมนต์เราสามารถแปลงมันเป็นฟังก์ชั่นหนึ่งอาร์กิวเมนต์โดยการปิดกั้น (ดูHaskell wiki ) และถ้าเรามีเพียงค่าx :: a
(ฟังก์ชั่นที่มี 0 อาร์กิวเมนต์) เราสามารถแปลงเป็นฟังก์ชันที่รับอาร์กิวเมนต์ของหน่วยประเภท :(\_ -> x) :: () -> a
:
เราสามารถสร้างโปรแกรมที่ซับซ้อนมากขึ้นในรูปแบบที่ง่ายขึ้นโดยการเขียนฟังก์ชั่นดังกล่าวโดยใช้.
โอเปอเรเตอร์ ตัวอย่างเช่นถ้าเรามีf :: a -> b
และเราได้รับg :: b -> c
g . f :: a -> c
โปรดทราบว่างานนี้ได้ค่าแปลงของเรามากเกินไป: ถ้าเรามีและแปลงเป็นตัวแทนของเราที่เราได้รับx :: a
f . ((\_ -> x) :: () -> a) :: () -> b
การเป็นตัวแทนนี้มีคุณสมบัติที่สำคัญมาก ได้แก่ :
- เรามีฟังก์ชั่นพิเศษมาก - ตัวตนของฟังก์ชั่นสำหรับแต่ละประเภท
id :: a -> a
a
มันเป็นเอกลักษณ์องค์ประกอบ ที่เกี่ยวกับ.
: f
เท่ากับทั้งสองและf . id
id . f
- ผู้ประกอบการองค์ประกอบการทำงาน
.
เป็นแบบเชื่อมโยง
การคำนวณแบบ monadic
สมมติว่าเราต้องการเลือกและทำงานกับการคำนวณหมวดหมู่พิเศษบางอย่างซึ่งผลลัพธ์มีบางสิ่งมากกว่าเพียงค่าส่งคืนเดียว เราไม่ต้องการระบุความหมายของ "สิ่งที่มากกว่า" เราต้องการให้สิ่งต่าง ๆ โดยทั่วไปเป็นไปได้ วิธีทั่วไปมากที่สุดเพื่อเป็นตัวแทนของ "บางสิ่งบางอย่างมากขึ้น" เป็นตัวแทนเป็นฟังก์ชั่นชนิด - ประเภทm
ของชนิด* -> *
(คือมันแปลงประเภทหนึ่งไปยังอีก) m :: * -> *
ดังนั้นสำหรับแต่ละประเภทของการคำนวณเราต้องการที่จะทำงานร่วมกับเราจะมีฟังก์ชั่นบางชนิด (ใน Haskell, m
คือ[]
, IO
, Maybe
ฯลฯ ) a -> m b
และประเภทที่ประสงค์มีฟังก์ชั่นทุกชนิด
ตอนนี้เราอยากจะทำงานกับฟังก์ชั่นในหมวดหมู่ดังกล่าวในลักษณะเดียวกันกับกรณีพื้นฐาน เราต้องการให้สามารถเขียนฟังก์ชั่นเหล่านี้ได้เราต้องการให้องค์ประกอบมีความสัมพันธ์และเราต้องการมีเอกลักษณ์ พวกเราต้องการ:
- จะมีผู้ประกอบการ (ขอเรียกว่า
<=<
) ที่ประกอบด้วยฟังก์ชั่นf :: a -> m b
และเป็นสิ่งที่เป็นg :: b -> m c
g <=< f :: a -> m c
และจะต้องมีการเชื่อมโยง
- จะมีฟังก์ชั่นบางตัวตนแต่ละประเภท,
return
ขอเรียกว่า นอกจากนี้เรายังต้องการที่f <=< return
เป็นเช่นเดียวกับและเช่นเดียวกับf
return <=< f
ใด ๆm :: * -> *
ที่เรามีฟังก์ชั่นดังกล่าวreturn
และ<=<
เรียกว่าmonad มันช่วยให้เราสร้างการคำนวณที่ซับซ้อนจากคนที่เรียบง่ายเช่นเดียวกับในกรณีพื้นฐาน แต่ตอนนี้ประเภทของค่าผลตอบแทนจะ tranformed m
โดย
(อันที่จริงฉันใช้คำว่าหมวดหมู่เล็กน้อยในทางที่ผิดในความหมายของหมวดหมู่ทฤษฎีเราสามารถเรียกหมวดหมู่การก่อสร้างของเราได้หลังจากที่เรารู้ว่ามันเป็นไปตามกฎหมายเหล่านี้)
Monads ใน Haskell
ใน Haskell (และภาษาการทำงานอื่น ๆ ) () -> a
เราส่วนใหญ่ทำงานกับค่าไม่ได้กับฟังก์ชั่นประเภท ดังนั้นแทนที่จะกำหนด<=<
สำหรับแต่ละ monad (>>=) :: m a -> (a -> m b) -> m b
เรากำหนดฟังก์ชั่น คำจำกัดความทางเลือกดังกล่าวเทียบเท่าเราสามารถแสดงการ>>=
ใช้<=<
และในทางกลับกัน (ลองออกกำลังกายหรือดูแหล่งที่มา ) หลักการคือตอนนี้ชัดเจนน้อยลง แต่ก็ยังคงเหมือนเดิม: ผลของเราอยู่เสมอประเภทและเราเขียนฟังก์ชั่นประเภทm a
a -> m b
สำหรับแต่ละ monad ที่เราสร้างเราต้องไม่ลืมที่จะตรวจสอบreturn
และ<=<
มีคุณสมบัติที่เราต้องการ: การเชื่อมโยงและตัวตนซ้าย / ขวา ใช้แสดงreturn
และ>>=
พวกเขาจะเรียกว่ากฎหมาย monad
ตัวอย่าง - รายการ
ถ้าเราเลือกm
ที่จะเป็นที่เราได้รับในประเภทของการทำงานของประเภท[]
a -> [b]
ฟังก์ชั่นดังกล่าวเป็นตัวแทนของการคำนวณที่ไม่ได้กำหนดค่าซึ่งผลลัพธ์อาจเป็นหนึ่งหรือหลายค่า แต่ก็ไม่มีค่าเช่นกัน นี้จะทำให้เกิดสิ่งที่เรียกว่าmonad รายการ องค์ประกอบf :: a -> [b]
และการg :: b -> [c]
ทำงานดังต่อไปนี้g <=< f :: a -> [c]
หมายถึงการคำนวณผลลัพธ์ที่เป็นไปได้ทั้งหมด[b]
นำg
ไปใช้กับแต่ละรายการและรวบรวมผลลัพธ์ทั้งหมดในรายการเดียว แสดงใน Haskell
return :: a -> [a]
return x = [x]
(<=<) :: (b -> [c]) -> (a -> [b]) -> (a -> [c])
g (<=<) f = concat . map g . f
หรือใช้ >>=
(>>=) :: [a] -> (a -> [b]) -> [b]
x >>= f = concat (map f x)
โปรดทราบว่าในตัวอย่างนี้ประเภทผลตอบแทนได้ดังนั้นมันจึงเป็นไปได้ว่าพวกเขาไม่ได้มีค่าของชนิดใด[a]
ๆ a
แน่นอนไม่มีข้อกำหนดดังกล่าวสำหรับ monad ที่ประเภทการคืนควรมีค่าเช่นนั้น monads บางคนมักจะมี (ชอบIO
หรือState
) แต่บางคนไม่เหมือนหรือ[]
Maybe
IO monad
อย่างที่ฉันได้กล่าวไปแล้วว่าIO
Monad นั้นค่อนข้างพิเศษ มูลค่าของประเภทIO a
หมายถึงค่าของประเภทที่a
สร้างขึ้นโดยการโต้ตอบกับสภาพแวดล้อมของโปรแกรม ดังนั้น (ต่างจากพระอื่น ๆ ทั้งหมด) เราไม่สามารถอธิบายคุณค่าของประเภทIO a
โดยใช้การก่อสร้างที่บริสุทธิ์ นี่IO
เป็นเพียงแท็กหรือป้ายกำกับที่แยกการคำนวณที่โต้ตอบกับสภาพแวดล้อม นี่คือ (กรณีเดียว) ที่มุมมอง # 1 และ # 2 ถูกต้อง
สำหรับIO
monad:
- องค์ประกอบ
f :: a -> IO b
และg :: b -> IO c
วิธีการ: คำนวณf
ที่มีปฏิสัมพันธ์กับสภาพแวดล้อมและจากนั้นคำนวณg
ที่ใช้ค่าและคำนวณผลลัพธ์ที่โต้ตอบกับสภาพแวดล้อม
return
เพียงเพิ่มIO
"แท็ก" ให้กับค่า (เราเพียง "คำนวณ" ผลลัพธ์โดยการรักษาสภาพแวดล้อมให้เหมือนเดิม)
- กฎหมาย monad (การเชื่อมโยงตัวตน) ได้รับการรับรองโดยคอมไพเลอร์
หมายเหตุบางส่วน:
- เนื่องจากการคำนวณแบบ monadic มีประเภทผลลัพธ์เสมอดังนั้น
m a
จึงไม่มีวิธี "หลบหนี" จากIO
monad ความหมายคือ: เมื่อการคำนวณโต้ตอบกับสภาพแวดล้อมคุณไม่สามารถสร้างการคำนวณจากสิ่งนั้นได้
- เมื่อโปรแกรมเมอร์ที่ใช้งานได้ไม่รู้ว่าจะทำบางสิ่งด้วยวิธีที่บริสุทธิ์ได้อย่างไรเขาสามารถ (เป็นทางเลือกสุดท้าย ) เขียนโปรแกรมโดยการคำนวณที่เป็นรัฐภายใน
IO
Monad นี่คือเหตุผลที่IO
มักจะเรียกว่าโปรแกรมเมอร์bin บาป
- ขอให้สังเกตว่าในโลกที่ไม่บริสุทธิ์ (ในแง่ของการเขียนโปรแกรมเชิงฟังก์ชัน) การอ่านค่าสามารถเปลี่ยนแปลงสภาพแวดล้อมได้เช่นกัน (เช่นใช้อินพุตของผู้ใช้) นั่นเป็นเหตุผลที่ฟังก์ชั่นเช่นต้องมีประเภทผลมาจากการ
getChar
IO something