เมื่อเร็ว ๆ นี้เมื่อดูที่ Haskell สิ่งที่จะเป็นคำอธิบายสั้น ๆ รวบรัดและเป็นประโยชน์เกี่ยวกับสิ่งที่เป็นหลัก monad คืออะไร?
ฉันพบว่าคำอธิบายส่วนใหญ่ที่ฉันเจอนั้นไม่สามารถเข้าถึงได้อย่างเป็นธรรมและขาดรายละเอียดในทางปฏิบัติ
เมื่อเร็ว ๆ นี้เมื่อดูที่ Haskell สิ่งที่จะเป็นคำอธิบายสั้น ๆ รวบรัดและเป็นประโยชน์เกี่ยวกับสิ่งที่เป็นหลัก monad คืออะไร?
ฉันพบว่าคำอธิบายส่วนใหญ่ที่ฉันเจอนั้นไม่สามารถเข้าถึงได้อย่างเป็นธรรมและขาดรายละเอียดในทางปฏิบัติ
คำตอบ:
อย่างแรก: คำว่าmonadนั้นค่อนข้างว่างเปล่าถ้าคุณไม่ใช่นักคณิตศาสตร์ ศัพท์ทางเลือกคือตัวสร้างการคำนวณซึ่งเป็นคำอธิบายที่มีประโยชน์มากกว่า
คุณขอตัวอย่างภาคปฏิบัติ:
ตัวอย่างที่ 1: รายการความเข้าใจ :
[x*2 | x<-[1..10], odd x]
การแสดงออกนี้จะส่งกลับจำนวนคู่คี่ทั้งหมดในช่วงจาก 1 ถึง 10 มีประโยชน์มาก!
ปรากฎว่านี่เป็นเพียงวากยสัมพันธ์สำหรับการดำเนินการบางอย่างภายใน List monad ความเข้าใจในรายการเดียวกันสามารถเขียนเป็น:
do
x <- [1..10]
guard (odd x)
return (x * 2)
หรือแม้กระทั่ง:
[1..10] >>= (\x -> guard (odd x) >> return (x*2))
ตัวอย่างที่ 2: อินพุต / เอาต์พุต :
do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Welcome, " ++ name ++ "!")
ทั้งสองตัวอย่างใช้ monads ผู้สร้างการคำนวณของ AKA ชุดรูปแบบทั่วไปคือ monad เชนการดำเนินการในบางวิธีเฉพาะมีประโยชน์ ในรายการความเข้าใจการดำเนินงานที่ถูกผูกมัดว่าหากการดำเนินการส่งกลับรายการแล้วการดำเนินการดังต่อไปนี้จะดำเนินการในทุกรายการในรายการ ในทางตรงกันข้าม IO monad ทำการดำเนินการตามลำดับ แต่ผ่าน "ตัวแปรที่ซ่อนอยู่" ตามซึ่งหมายถึง "สถานะของโลก" ซึ่งช่วยให้เราสามารถเขียนรหัส I / O ในลักษณะการทำงานที่บริสุทธิ์
มันกลับกลายเป็นว่ารูปแบบของการปฏิบัติการผูกมัดค่อนข้างมีประโยชน์และใช้สำหรับสิ่งต่าง ๆ มากมายใน Haskell
อีกตัวอย่างหนึ่งคือข้อยกเว้น: การใช้Error
monad การดำเนินการจะถูกล่ามโซ่ซึ่งจะถูกดำเนินการตามลำดับยกเว้นว่ามีข้อผิดพลาดเกิดขึ้นในกรณีที่ส่วนที่เหลือของโซ่ถูกทอดทิ้ง
ทั้งไวยากรณ์ list-comprehension และ do-notation เป็น syntactic sugar สำหรับการดำเนินการโยงโดยใช้>>=
โอเปอเรเตอร์ Monad นั้นเป็นเพียงประเภทที่รองรับ>>=
ผู้ปฏิบัติงาน
ตัวอย่างที่ 3: ตัวแยกวิเคราะห์
นี่เป็นตัวแยกวิเคราะห์ที่ง่ายมากซึ่งแยกวิเคราะห์สตริงที่ยกมาหรือจำนวน:
parseExpr = parseString <|> parseNumber
parseString = do
char '"'
x <- many (noneOf "\"")
char '"'
return (StringValue x)
parseNumber = do
num <- many1 digit
return (NumberValue (read num))
การดำเนินงานchar
, digit
ฯลฯ สวยเรียบง่าย ไม่ว่าจะตรงกันหรือไม่ตรงกัน เวทย์มนตร์เป็น monad ที่จัดการโฟลว์การควบคุม: การดำเนินการจะดำเนินการตามลำดับจนกว่าการแข่งขันจะล้มเหลวซึ่งในกรณีนี้ backtracks monad เป็นเวอร์ชั่นล่าสุด<|>
และลองใช้ตัวเลือกถัดไป อีกครั้งวิธีการผูกมัดการดำเนินงานที่มีความหมายเพิ่มเติมที่มีประโยชน์
ตัวอย่างที่ 4: การเขียนโปรแกรมแบบอะซิงโครนัส
ตัวอย่างข้างต้นอยู่ใน Haskell แต่ปรากฎว่าF #รองรับ monads ด้วยเช่นกัน ตัวอย่างนี้ถูกขโมยจากDon Syme :
let AsyncHttp(url:string) =
async { let req = WebRequest.Create(url)
let! rsp = req.GetResponseAsync()
use stream = rsp.GetResponseStream()
use reader = new System.IO.StreamReader(stream)
return reader.ReadToEnd() }
วิธีนี้ดึงข้อมูลเว็บเพจ บรรทัดการเจาะคือการใช้GetResponseAsync
- จริง ๆ แล้วจะรอการตอบสนองในเธรดแยกต่างหากในขณะที่เธรดหลักส่งกลับจากฟังก์ชัน บรรทัดสามบรรทัดสุดท้ายจะถูกดำเนินการบนเธรด spawned เมื่อได้รับการตอบกลับ
ในภาษาอื่น ๆ ส่วนใหญ่คุณจะต้องสร้างฟังก์ชั่นแยกต่างหากสำหรับบรรทัดที่จัดการกับการตอบสนอง async
monad สามารถ "แยก" บล็อกของตัวเองและเลื่อนการดำเนินการในช่วงครึ่งหลัง ( async {}
ไวยากรณ์ระบุว่าการควบคุมโฟลว์ในบล็อกถูกกำหนดโดยasync
monad)
พวกเขาทำงานอย่างไร
ดังนั้น Monad จะทำสิ่งที่ควบคุมการไหลของแฟนซีเหล่านี้ได้อย่างไร? สิ่งที่เกิดขึ้นจริงใน do-block (หรือนิพจน์การคำนวณตามที่ถูกเรียกใน F #) คือทุกการดำเนินการ (โดยทั่วไปทุกบรรทัด) ถูกห่อในฟังก์ชันที่ไม่ระบุชื่อแยกต่างหาก ฟังก์ชั่นเหล่านี้จะถูกรวมเข้าด้วยกันโดยใช้bind
โอเปอเรเตอร์ (สะกด>>=
ใน Haskell) เนื่องจากการbind
ดำเนินการรวมฟังก์ชั่นเข้าด้วยกันมันจึงสามารถเรียกใช้งานได้ตามที่เห็นสมควร: เรียงตามลำดับหลายครั้งในทางกลับกันยกเลิกบางส่วนเรียกใช้งานบางส่วนในเธรดแยกต่างหากเมื่อรู้สึกเช่นนั้นเป็นต้น
เป็นตัวอย่างนี่เป็นเวอร์ชันที่ขยายของ IO-code จากตัวอย่างที่ 2:
putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))
นี่เป็นสิ่งที่น่าเกลียด แต่ก็มีความชัดเจนมากขึ้นว่าเกิดอะไรขึ้น >>=
ประกอบเป็นส่วนผสมมายากล: มันต้องใช้ค่า (ด้านซ้าย) และรวมกับฟังก์ชั่น (ด้านขวา) ในการผลิตค่าใหม่ ค่าใหม่นี้จะถูกใช้โดย>>=
โอเปอเรเตอร์ถัดไปและรวมกับฟังก์ชันเพื่อสร้างค่าใหม่ >>=
สามารถดูได้ในฐานะผู้ประเมินผลขนาดเล็ก
โปรดทราบว่า>>=
มากเกินไปสำหรับประเภทที่แตกต่างกันเพื่อให้ทุก monad >>=
มีการดำเนินการของตัวเองของ (การดำเนินการทั้งหมดในห่วงโซ่จะต้องเป็นประเภทของ monad เดียวกันแม้ว่ามิฉะนั้น>>=
ผู้ประกอบการจะไม่ทำงาน)
การใช้งานที่ง่ายที่สุด>>=
เพียงแค่นำค่าทางด้านซ้ายมาใช้กับฟังก์ชั่นทางด้านขวาและส่งคืนผลลัพธ์ แต่อย่างที่กล่าวไว้ก่อนหน้านี้สิ่งที่ทำให้รูปแบบทั้งหมดมีประโยชน์คือเมื่อมีสิ่งพิเศษเกิดขึ้นในการดำเนินงานของ monad >>=
.
มีความฉลาดเพิ่มเติมบางอย่างในวิธีการส่งผ่านค่าจากการดำเนินการหนึ่งไปยังการดำเนินการต่อไป แต่สิ่งนี้ต้องการคำอธิบายที่ลึกกว่าของระบบประเภท Haskell
สรุป
ในข้อตกลง Haskell monad เป็นประเภทพารามิเตอร์ซึ่งเป็นตัวอย่างของคลาสประเภท Monad ซึ่งกำหนด>>=
พร้อมกับผู้ประกอบการอื่น ๆ ไม่กี่ ในแง่ของคนธรรมดา monad เป็นเพียงประเภทที่>>=
กำหนดการดำเนินการ
ในตัวมันเอง>>=
เป็นวิธีที่ยุ่งยากในการผูกมัดฟังก์ชั่น แต่ด้วยการปรากฏตัวของการทำเครื่องหมายที่ซ่อน "ประปา" การดำเนินการ monadic กลายเป็นนามธรรมที่ดีและมีประโยชน์มีหลายสถานที่ในภาษาและมีประโยชน์ สำหรับการสร้างมินิภาษาของคุณเองในภาษา
เหตุใดพระจึงแข็ง
สำหรับผู้เรียนที่ Haskell หลายคนพระเป็นอุปสรรคที่พวกเขาชนราวกับกำแพงอิฐ ไม่ใช่ว่าตัวเองมีความซับซ้อน แต่การใช้งานนั้นขึ้นอยู่กับคุณลักษณะขั้นสูงอื่น ๆ ของ Haskell เช่นประเภทที่กำหนดพารามิเตอร์คลาสประเภทและอื่น ๆ ปัญหาคือว่า Haskell I / O ขึ้นอยู่กับ monads และ I / O อาจเป็นหนึ่งในสิ่งแรกที่คุณต้องการทำความเข้าใจเมื่อเรียนรู้ภาษาใหม่ - หลังจากทั้งหมดมันไม่สนุกเลยที่จะสร้างโปรแกรมที่ไม่ได้ผลิตอะไรเลย เอาท์พุต ฉันไม่มีวิธีแก้ไขปัญหาสำหรับไก่และไข่ในทันทีนี้ยกเว้นการปฏิบัติต่อ I / O เช่น "เวทมนต์เกิดขึ้นที่นี่" จนกว่าคุณจะมีประสบการณ์เพียงพอกับส่วนอื่น ๆ ของภาษา ขอโทษ
บล็อกที่ยอดเยี่ยมเกี่ยวกับ monads: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
การอธิบายว่า "monad คืออะไร" ก็เหมือนกับการพูดว่า "ตัวเลขคืออะไร" เราใช้ตัวเลขตลอดเวลา แต่ลองจินตนาการว่าคุณได้พบกับคนที่ไม่รู้จักอะไรเกี่ยวกับตัวเลข วิธีห่าคุณจะอธิบายว่าตัวเลขจะอยู่ที่ไหน? และคุณจะเริ่มอธิบายได้อย่างไรว่าทำไมถึงมีประโยชน์
Monad คืออะไร คำตอบสั้น ๆ : มันเป็นวิธีที่เฉพาะเจาะจงของการผูกมัดการทำงานร่วมกัน
ในสาระสำคัญคุณกำลังเขียนขั้นตอนการดำเนินการและเชื่อมโยงพวกเขาเข้าด้วยกันกับ "ฟังก์ชั่นการผูก" (ใน Haskell มีชื่อ>>=
) คุณสามารถเขียนการเรียกไปยังโอเปอเรเตอร์ bind ด้วยตัวคุณเองหรือคุณสามารถใช้ซินแท็กซ์น้ำตาลซึ่งทำให้คอมไพเลอร์แทรกการเรียกฟังก์ชันเหล่านั้นให้คุณ แต่อย่างใดแต่ละขั้นตอนจะถูกคั่นด้วยการเรียกไปยังฟังก์ชันการโยงนี้
ดังนั้นฟังก์ชั่นการผูกจึงเป็นเครื่องหมายอัฒภาค; มันแยกขั้นตอนในกระบวนการ หน้าที่ของฟังก์ชั่น bind คือการเอาท์พุทจากขั้นตอนก่อนหน้าและป้อนเข้าสู่ขั้นตอนถัดไป
นั่นไม่ฟังดูยากเกินไปใช่มั้ย แต่มีmonad มากกว่าหนึ่งชนิด ทำไม? อย่างไร?
ฟังก์ชั่นการผูกสามารถนำผลลัพธ์มาจากขั้นตอนเดียวและป้อนเข้าสู่ขั้นตอนถัดไป แต่ถ้านั่นคือ "ทั้งหมด" monad ทำ ... นั่นไม่ได้มีประโยชน์จริงๆ และนั่นเป็นสิ่งสำคัญที่จะต้องเข้าใจ: monad ที่มีประโยชน์ทำอย่างอื่นนอกเหนือจากการเป็น monad monad ที่มีประโยชน์ทุกคนมี "พลังพิเศษ" ซึ่งทำให้เป็นเอกลักษณ์
(Monad ที่ไม่ทำสิ่งใดเป็นพิเศษเรียกว่า "identity monad" แทนที่จะฟังก์ชั่นเอกลักษณ์นี้ดูเหมือนจะเป็นสิ่งที่ไร้จุดหมาย แต่กลับกลายเป็นว่าไม่ใช่ ... แต่นั่นเป็นอีกเรื่อง™)
โดยทั่วไปแต่ละ monad จะมีฟังก์ชั่นการผูกของตัวเอง และคุณสามารถเขียนฟังก์ชั่นการเชื่อมโยงเพื่อให้มันวนรอบสิ่งต่าง ๆ ระหว่างขั้นตอนการดำเนินการ ตัวอย่างเช่น:
หากแต่ละขั้นตอนส่งกลับตัวบ่งชี้ความสำเร็จ / ความล้มเหลวคุณสามารถผูกดำเนินการขั้นตอนถัดไปได้หากขั้นตอนก่อนหน้าประสบความสำเร็จ ด้วยวิธีนี้ขั้นตอนที่ล้มเหลวจะยกเลิกลำดับทั้งหมด "โดยอัตโนมัติ" โดยไม่มีการทดสอบตามเงื่อนไขจากคุณ ( ความล้มเหลว Monad )
การขยายแนวคิดนี้คุณสามารถใช้ "ข้อยกเว้น" ได้ ( Monad ข้อผิดพลาดหรือข้อยกเว้น Monad ) เนื่องจากคุณกำหนดด้วยตนเองแทนที่จะเป็นคุณลักษณะภาษาคุณจึงสามารถกำหนดวิธีการทำงานได้ (เช่นคุณอาจต้องการละเว้นข้อยกเว้นสองข้อแรกและยกเลิกเฉพาะเมื่อมีการโยนข้อยกเว้นที่สาม )
คุณสามารถทำให้แต่ละขั้นตอนส่งคืนผลลัพธ์ได้หลายรายการและมีฟังก์ชันผูกการวนซ้ำพวกเขาแต่ละรายการเข้าสู่ขั้นตอนถัดไปสำหรับคุณ ด้วยวิธีนี้คุณไม่จำเป็นต้องเขียนวนซ้ำไปทั่วทุกที่เมื่อจัดการกับผลลัพธ์หลายรายการ ฟังก์ชั่นผูก "อัตโนมัติ" จะทำทุกอย่างให้คุณ ( รายการ Monad )
เช่นเดียวกับการส่งผ่าน "ผลลัพธ์" จากขั้นตอนหนึ่งไปอีกขั้นคุณสามารถให้ฟังก์ชันการเชื่อมโยงส่งผ่านข้อมูลพิเศษได้เช่นกัน ข้อมูลนี้ไม่แสดงในซอร์สโค้ดของคุณ แต่คุณยังสามารถเข้าถึงได้จากทุกที่โดยไม่ต้องส่งต่อไปยังทุกฟังก์ชั่นด้วยตนเอง (ผู้อ่าน Monad )
คุณสามารถทำให้มันเพื่อให้ "ข้อมูลพิเศษ" สามารถถูกแทนที่ สิ่งนี้จะช่วยให้คุณจำลองการอัพเดทแบบทำลายได้โดยไม่ต้องทำการอัพเดทแบบทำลาย ( รัฐโมนาดและลูกพี่ลูกน้องนักเขียนโมนาด)
เนื่องจากคุณจำลองการอัปเดตที่มีการทำลายเท่านั้นคุณสามารถทำสิ่งต่าง ๆ ที่เป็นไปไม่ได้ด้วยการอัปเดตที่ทำลายล้างจริง ตัวอย่างเช่นคุณสามารถเลิกทำการอัปเดตครั้งล่าสุดหรือเปลี่ยนกลับเป็นเวอร์ชันเก่ากว่าได้
คุณสามารถสร้าง monad ที่สามารถหยุดการคำนวณได้เพื่อให้คุณสามารถหยุดโปรแกรมของคุณชั่วคราวเข้าไปในและคนจรจัดกับข้อมูลสถานะภายในแล้วดำเนินการต่อ
คุณสามารถใช้ "การสืบเนื่อง" เป็น monad สิ่งนี้ช่วยให้คุณสามารถทำลายจิตใจของผู้คน!
ทั้งหมดนี้และอื่น ๆ เป็นไปได้กับ monads แน่นอนว่าทั้งหมดนี้เป็นไปได้อย่างสมบูรณ์แบบโดยไม่ต้อง monads เกินไป มันง่ายขึ้นมากเมื่อใช้ monads
ที่จริงตรงกันข้ามกับความเข้าใจทั่วไปของ Monads พวกเขาไม่มีอะไรเกี่ยวข้องกับรัฐ Monads เป็นเพียงวิธีหนึ่งในการห่อสิ่งต่าง ๆ และจัดเตรียมวิธีดำเนินการกับสิ่งที่ถูกห่อโดยไม่ต้องเปิดมัน
ตัวอย่างเช่นคุณสามารถสร้างประเภทเพื่อห่ออีกประเภทหนึ่งใน Haskell:
data Wrapped a = Wrap a
เพื่อห่อสิ่งที่เรากำหนด
return :: a -> Wrapped a
return x = Wrap x
หากต้องการดำเนินการโดยไม่ต้องคลายออกให้พูดว่าคุณมีฟังก์ชั่นf :: a -> b
จากนั้นคุณสามารถทำเช่นนี้เพื่อยกฟังก์ชั่นนั้นขึ้นมาทำหน้าที่ห่อค่า:
fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)
นั่นคือทั้งหมดที่เกี่ยวกับการทำความเข้าใจ อย่างไรก็ตามปรากฎว่ามีฟังก์ชั่นทั่วไปมากขึ้นในการยกนี้ซึ่งก็คือbind
:
bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x
bind
สามารถทำอะไรได้มากกว่าเล็กน้อยfmap
แต่ไม่ใช่ในทางกลับกัน ที่จริงfmap
สามารถกำหนดเพียง แต่ในแง่ของและbind
return
ดังนั้นเมื่อกำหนด monad .. คุณให้ประเภทของมัน (นี่มันWrapped a
) แล้วพูดว่ามันทำงานอย่างไรreturn
และbind
การดำเนินงาน
สิ่งที่ยอดเยี่ยมคือสิ่งนี้กลายเป็นรูปแบบทั่วไปที่ปรากฏขึ้นทั่วสถานที่การห่อหุ้มสถานะในทางที่บริสุทธิ์เป็นเพียงหนึ่งในนั้น
สำหรับบทความที่ดีเกี่ยวกับวิธี monads สามารถนำมาใช้ในการแนะนำการทำงานและการอ้างอิงจึงควบคุมลำดับของการประเมินผลเช่นนั้นจะใช้ใน Haskell ของ IO monad ตรวจสอบIO ภายใน
สำหรับการทำความเข้าใจกับพระไม่ต้องกังวลมากเกินไป อ่านเกี่ยวกับสิ่งที่คุณสนใจและไม่ต้องกังวลหากคุณไม่เข้าใจในทันที จากนั้นเพียงดำน้ำในภาษาเช่น Haskell เป็นวิธีที่จะไป Monads เป็นหนึ่งในสิ่งเหล่านี้ที่การทำความเข้าใจกับสมองของคุณโดยการฝึกฝนวันหนึ่งคุณก็รู้ตัวว่าคุณเข้าใจพวกเขา
แต่คุณสามารถคิดค้น Monads ได้!
sigfpe พูดว่า:
แต่สิ่งเหล่านี้นำเสนอพระสงฆ์เป็นสิ่งลึกลับที่ต้องการคำอธิบาย แต่สิ่งที่ฉันต้องการโต้แย้งก็คือพวกเขาไม่ได้เป็นความลับเลย ในความเป็นจริงต้องเผชิญกับปัญหาต่าง ๆ ในการเขียนโปรแกรมการทำงานที่คุณจะได้รับนำไปสู่การแก้ปัญหาบางอย่างซึ่งทั้งหมดนี้เป็นตัวอย่างของพระ อันที่จริงฉันหวังว่าจะให้คุณประดิษฐ์มันขึ้นมาในตอนนี้ถ้าคุณยังไม่ได้ทำ จากนั้นเป็นขั้นตอนเล็ก ๆ ที่จะสังเกตเห็นว่าโซลูชั่นเหล่านี้ทั้งหมดเป็นวิธีการแก้ปัญหาเดียวกันในการปลอม และหลังจากอ่านสิ่งนี้แล้วคุณอาจอยู่ในตำแหน่งที่ดีขึ้นในการทำความเข้าใจกับเอกสารอื่น ๆ ในพระเพราะคุณจำทุกสิ่งที่คุณเห็นว่าเป็นสิ่งที่คุณประดิษฐ์ขึ้นมาแล้ว
ปัญหาหลายอย่างที่ monads พยายามแก้ไขเกี่ยวข้องกับปัญหาของผลข้างเคียง ดังนั้นเราจะเริ่มด้วย (โปรดทราบว่า monads อนุญาตให้คุณทำมากกว่าจัดการกับผลข้างเคียงโดยเฉพาะอย่างยิ่งวัตถุคอนเทนเนอร์หลายประเภทสามารถดูได้เป็น monads การแนะนำบางอย่างให้ monads พบว่าเป็นการยากที่จะกระทบยอดการใช้ monads ที่แตกต่างกันทั้งสองนี้ อื่น ๆ.)
ในภาษาการเขียนโปรแกรมที่จำเป็นเช่น C ++ ฟังก์ชั่นจะทำงานไม่เหมือนกับฟังก์ชันของคณิตศาสตร์ ตัวอย่างเช่นสมมติว่าเรามีฟังก์ชั่น C ++ ที่รับอาร์กิวเมนต์ floating point เพียงตัวเดียวและส่งกลับผลลัพธ์ของ floating point เผินๆมันอาจฟังดูคล้ายฟังก์ชั่นการคำนวณทางคณิตศาสตร์เพื่อ reals แต่ฟังก์ชั่น C ++ สามารถทำได้มากกว่าเพียงแค่ส่งกลับตัวเลขที่ขึ้นอยู่กับข้อโต้แย้งของมัน มันสามารถอ่านและเขียนค่าของตัวแปรทั่วโลกเช่นเดียวกับการเขียนผลลัพธ์ไปยังหน้าจอและรับอินพุตจากผู้ใช้ อย่างไรก็ตามในภาษาที่ใช้งานได้จริงฟังก์ชั่นสามารถอ่านได้เฉพาะสิ่งที่มีให้ในการโต้แย้งและวิธีเดียวที่มันจะมีผลต่อโลกคือผ่านค่าที่ส่งคืน
Monad เป็นประเภทข้อมูลที่มีสองการดำเนินการ: >>=
(aka bind
) และreturn
(aka unit
) return
รับค่าตามอำเภอใจและสร้างอินสแตนซ์ของ monad ด้วย >>=
ใช้อินสแตนซ์ของ monad และแมปฟังก์ชันกับมัน (คุณสามารถเห็นแล้วว่า monad เป็นประเภทข้อมูลที่แปลกเนื่องจากในภาษาการเขียนโปรแกรมส่วนใหญ่คุณไม่สามารถเขียนฟังก์ชั่นที่มีค่าตามอำเภอใจและสร้างประเภทจากมัน Monads ใช้ประเภทของตัวแปรที่หลากหลาย )
ในสัญกรณ์ Haskell อินเตอร์เฟส monad จะถูกเขียน
class Monad m where
return :: a -> m a
(>>=) :: forall a b . m a -> (a -> m b) -> m b
การดำเนินการเหล่านี้ควรจะเชื่อฟัง "กฎหมาย" บางอย่าง แต่นั่นไม่สำคัญอย่างยิ่งยวด: "กฎหมาย" เพียงประมวลวิธีการใช้งานที่สมเหตุสมผลของการดำเนินการที่ควรปฏิบัติ (โดยพื้นฐานแล้ว>>=
และreturn
ควรจะเห็นด้วยว่า นั่น>>=
คือการเชื่อมโยง)
Monads ไม่ได้เป็นเพียงเกี่ยวกับสถานะและ I / O: พวกเขานามธรรมรูปแบบทั่วไปของการคำนวณที่รวมถึงการทำงานกับรัฐ I / O ข้อยกเว้นและไม่ใช่ระดับ อาจเป็นพระที่เข้าใจง่ายที่สุดคือรายการและประเภทตัวเลือก:
instance Monad [ ] where
[] >>= k = []
(x:xs) >>= k = k x ++ (xs >>= k)
return x = [x]
instance Monad Maybe where
Just x >>= k = k x
Nothing >>= k = Nothing
return x = Just x
โดยที่[]
และ:
เป็นตัวสร้างรายการ++
คือตัวดำเนินการเรียงต่อกันและJust
และNothing
เป็นตัวMaybe
สร้าง Monads ทั้งสองนี้มีรูปแบบการคำนวณทั่วไปและเป็นประโยชน์ในประเภทข้อมูลที่เกี่ยวข้อง (โปรดทราบว่าไม่มีอะไรเกี่ยวข้องกับผลข้างเคียงหรือ I / O)
คุณต้องลองใช้การเขียนรหัส Haskell ที่ไม่สำคัญเพื่อชื่นชมว่า monads นั้นเกี่ยวกับอะไรและทำไมจึงมีประโยชน์
ก่อนอื่นคุณควรเข้าใจว่านักแสดงคืออะไร ก่อนหน้านั้นเข้าใจฟังก์ชั่นการสั่งซื้อที่สูงขึ้น
ฟังก์ชั่นขั้นสูงเป็นเพียงฟังก์ชั่นที่ใช้ฟังก์ชั่นเป็นอาร์กิวเมนต์
functorคือการก่อสร้างทุกประเภทT
ที่มีอยู่ฟังก์ชั่นขั้นสูงเรียกว่าmap
แปลงที่ฟังก์ชั่นของชนิดa -> b
(ใดก็ตามทั้งสองประเภทa
และb
) T a -> T b
ลงในฟังก์ชั่น map
ฟังก์ชั่นนี้จะต้องเชื่อฟังกฎหมายของตัวตนและองค์ประกอบเช่นว่าการแสดงออกต่อไปนี้กลับเป็นจริงสำหรับทุกคนp
และq
(สัญกรณ์ Haskell):
map id = id
map (p . q) = map p . map q
ตัวอย่างเช่นตัวสร้างประเภทที่เรียกว่าList
เป็น functor ถ้ามันมาพร้อมกับฟังก์ชั่นของประเภท(a -> b) -> List a -> List b
ที่ปฏิบัติตามกฎหมายข้างต้น การใช้งานจริงเท่านั้นที่เห็นได้ชัด List a -> List b
ฟังก์ชันที่เป็นผลลัพธ์จะวนซ้ำตามรายการที่กำหนดเรียกใช้(a -> b)
ฟังก์ชันสำหรับแต่ละองค์ประกอบและส่งคืนรายการผลลัพธ์
monadเป็นหลักเพียง functor T
มีสองวิธีพิเศษjoin
ของชนิดT (T a) -> T a
และunit
(บางครั้งเรียกว่าreturn
, fork
หรือpure
) a -> T a
ประเภท สำหรับรายการใน Haskell:
join :: [[a]] -> [a]
pure :: a -> [a]
ทำไมจึงมีประโยชน์ เพราะคุณสามารถยกตัวอย่างเช่นmap
เหนือรายการที่มีฟังก์ชั่นที่ส่งกลับรายการ Join
ใช้รายการผลลัพธ์ของรายการและเชื่อมโยงรายการ List
เป็น monad เพราะเป็นไปได้
คุณสามารถเขียนฟังก์ชั่นที่ทำmap
เช่นนั้นjoin
ได้ ฟังก์ชั่นนี้จะเรียกว่าbind
หรือflatMap
หรือหรือ(>>=)
(=<<)
นี่คือวิธีที่อินสแตนซ์ monad ได้รับใน Haskell
Monad ต้องปฏิบัติตามกฎหมายบางประการกล่าวคือjoin
จะต้องเชื่อมโยงกัน ซึ่งหมายความว่าถ้าคุณมีค่าx
ประเภท[[[a]]]
แล้วควรจะเท่ากับjoin (join x)
join (map join x)
และpure
จะต้องเป็นตัวตนสำหรับดังกล่าวว่าjoin
join (pure x) == x
[ข้อจำกัดความรับผิดชอบ: ฉันยังคงพยายามที่จะบวชอย่างเต็มที่ ต่อไปนี้เป็นเพียงสิ่งที่ฉันเข้าใจ ถ้ามันผิดหวังว่าคนที่มีความรู้จะโทรหาฉันบนพรม]
Arnar เขียนว่า:
Monads เป็นเพียงวิธีหนึ่งในการห่อสิ่งต่าง ๆ และจัดเตรียมวิธีดำเนินการกับสิ่งที่ถูกห่อโดยไม่ต้องเปิดมัน
นั่นแหละ แนวคิดจะเป็นดังนี้:
คุณรับค่าบางอย่างและห่อด้วยข้อมูลเพิ่มเติม เช่นเดียวกับค่าที่เป็นชนิดบางอย่าง (เช่นจำนวนเต็มหรือสตริง) ดังนั้นข้อมูลเพิ่มเติมจึงเป็นชนิดที่แน่นอน
เช่นว่าข้อมูลเพิ่มเติมอาจจะมีหรือMaybe
IO
จากนั้นคุณมีผู้ให้บริการบางรายที่อนุญาตให้คุณดำเนินการกับข้อมูลที่ถูกห่อในขณะที่ดำเนินการพร้อมกับข้อมูลเพิ่มเติมนั้น ตัวดำเนินการเหล่านี้ใช้ข้อมูลเพิ่มเติมเพื่อตัดสินใจว่าจะเปลี่ยนลักษณะการทำงานของการดำเนินการกับค่าที่ถูกห่อ
เช่นที่Maybe Int
อาจจะเป็นหรือJust Int
Nothing
ตอนนี้ถ้าคุณเพิ่มMaybe Int
ไปยังMaybe Int
ผู้ประกอบการจะตรวจสอบเพื่อดูว่าพวกเขามีทั้งที่Just Int
อยู่ภายในและถ้าเป็นเช่นนั้นจะแกะInt
s, ผ่านพวกเขาดำเนินการนอกจากนี้อีกห่อส่งผลให้Int
เข้าใหม่Just Int
(ซึ่งเป็นที่ถูกต้องMaybe Int
) Maybe Int
และทำให้กลับมาเป็น แต่ถ้าหนึ่งของพวกเขาเป็นNothing
ภายในดำเนินการนี้ก็จะกลับทันทีอีกครั้งซึ่งเป็นที่ถูกต้องNothing
Maybe Int
ด้วยวิธีนี้คุณสามารถทำเป็นว่าคุณMaybe Int
เป็นเพียงตัวเลขปกติและทำการคำนวณทางคณิตศาสตร์กับพวกมัน หากคุณกำลังจะได้รับNothing
สมการของคุณจะยังคงผลิตผลขวา - โดยคุณไม่ต้องตรวจสอบครอกสำหรับNothing
ทุก
Maybe
แต่ตัวอย่างเป็นเพียงสิ่งที่เกิดขึ้น หากข้อมูลเพิ่มเติมเป็นแบบIO
นั้นตัวดำเนินการพิเศษที่กำหนดไว้สำหรับIO
s จะถูกเรียกแทนและมันสามารถทำบางสิ่งที่แตกต่างออกไปโดยสิ้นเชิงก่อนที่จะทำการเพิ่ม (ตกลงการรวมสองIO Int
s เข้าด้วยกันอาจเป็นเรื่องไร้สาระ - ฉันยังไม่แน่ใจ) (นอกจากนี้หากคุณให้ความสนใจกับMaybe
ตัวอย่างคุณสังเกตว่า "การตัดค่าด้วยสิ่งพิเศษ" นั้นไม่ถูกต้องเสมอไป แต่มันยาก เป็นที่แน่นอนถูกต้องและแม่นยำโดยไม่ต้องมีการพิสูจน์ไม่ได้)
โดยพื้นฐานแล้ว“ monad” หมายถึง“ รูปแบบ”อย่างคร่าว ๆ แต่แทนที่จะหนังสือที่เต็มไปด้วยรูปแบบการอธิบายอย่างไม่เป็นทางการและตั้งชื่อโดยเฉพาะขณะนี้คุณมีภาษาสร้าง - ไวยากรณ์และทั้งหมด - ที่ช่วยให้คุณประกาศรูปแบบใหม่เป็นสิ่งที่อยู่ในโปรแกรมของคุณ (ความไม่แน่ชัดที่นี่คือรูปแบบทั้งหมดจะต้องเป็นไปตามรูปแบบเฉพาะดังนั้น monad จึงไม่ธรรมดาเหมือนรูปแบบ แต่ฉันคิดว่านั่นเป็นคำที่ใกล้เคียงที่สุดที่คนส่วนใหญ่รู้จักและเข้าใจ)
และนั่นคือสาเหตุที่ผู้คนพบว่าพระสงฆ์สับสนมากเพราะเป็นแนวคิดทั่วไป การถามสิ่งที่ทำให้บางสิ่งบางอย่างเป็นแบบ monad คลุมเครือเหมือนกับการถามว่าอะไรที่ทำให้รูปแบบเป็นอะไร
แต่คิดว่าผลกระทบของการมีการสนับสนุนทางไวยากรณ์ในภาษาสำหรับแนวคิดของรูปแบบ: แทนที่จะต้องอ่านหนังสือGang of Fourและจดจำการสร้างรูปแบบเฉพาะคุณเพียงแค่เขียนรหัสที่ใช้รูปแบบนี้ในผู้ไม่เชื่อเรื่องพระเจ้า วิธีทั่วไปครั้งเดียวแล้วคุณจะทำ! จากนั้นคุณสามารถนำรูปแบบนี้กลับมาใช้ใหม่เช่นผู้เยี่ยมชมหรือกลยุทธ์หรือส่วนหน้าหรืออะไรก็ตามเพียงแค่ตกแต่งการดำเนินการในโค้ดของคุณด้วยโดยไม่ต้องนำมันไปใช้ซ้ำแล้วซ้ำอีก!
นั่นคือสาเหตุที่คนที่เข้าใจพระพบว่าพวกเขามีประโยชน์มาก : มันไม่ใช่แนวคิดหอคอยงาช้างที่ snobs ทางปัญญาภูมิใจในการทำความเข้าใจ (ตกลงแน่นอนเกินไป teehee) แต่จริงๆแล้วทำให้โค้ดง่ายขึ้น
M (M a) -> M a
บางสิ่งบางอย่างคือการดำรงอยู่ของฟังก์ชั่นที่มีชนิด ความจริงที่ว่าคุณสามารถเปลี่ยนให้เป็นประเภทหนึ่งM a -> (a -> M b) -> M b
เป็นสิ่งที่ทำให้พวกเขามีประโยชน์
หลังจากพยายามมากฉันคิดว่าในที่สุดฉันก็เข้าใจ monad หลังจากอ่านคำวิจารณ์ที่ยาวเหยียดของตัวเองเพื่อหาคำตอบที่ได้รับการโหวตมากที่สุดฉันจะให้คำอธิบายนี้
มีคำถามสามข้อที่ต้องตอบให้เข้าใจพระ:
ดังที่ฉันได้กล่าวไว้ในความคิดเห็นดั้งเดิมของฉันคำอธิบายของ monad จำนวนมากเกินไปนั้นเกิดขึ้นในคำถามข้อ 3 โดยไม่ต้องและก่อนที่จะครอบคลุมคำถาม 2 หรือคำถาม 1 อย่างเพียงพอ
ทำไมคุณต้องใช้ Monad
ภาษาที่ใช้งานได้จริงเช่น Haskell นั้นแตกต่างจากภาษาที่จำเป็นเช่น C หรือ Java ซึ่งโปรแกรมที่ใช้งานได้จริงนั้นไม่จำเป็นต้องดำเนินการตามลำดับที่เฉพาะเจาะจงทีละขั้นตอน โปรแกรม Haskell นั้นคล้ายกับฟังก์ชั่นทางคณิตศาสตร์มากกว่าซึ่งคุณอาจแก้ "สมการ" ในจำนวนคำสั่งที่อาจเกิดขึ้นได้ สิ่งนี้ช่วยให้คุณได้รับประโยชน์มากมายโดยที่มันกำจัดโอกาสที่จะเกิดข้อผิดพลาดบางประเภทโดยเฉพาะอย่างยิ่งสิ่งที่เกี่ยวข้องกับสิ่งต่าง ๆ เช่น "สถานะ"
อย่างไรก็ตามมีปัญหาบางอย่างที่ไม่สามารถแก้ไขได้ด้วยการเขียนโปรแกรมแบบนี้ บางสิ่งเช่นการเขียนโปรแกรมคอนโซลและไฟล์ i / o ต้องการสิ่งที่จะเกิดขึ้นตามลำดับที่ระบุหรือต้องการรักษาสถานะ วิธีหนึ่งในการจัดการกับปัญหานี้คือการสร้างชนิดของวัตถุที่แสดงถึงสถานะของการคำนวณและชุดของฟังก์ชันที่ใช้วัตถุสถานะเป็นอินพุตและส่งคืนวัตถุสถานะที่ปรับเปลี่ยนใหม่
ดังนั้นเรามาสร้างค่า "สถานะ" สมมุติฐานที่แสดงถึงสถานะของหน้าจอคอนโซล วิธีการสร้างค่านี้ไม่สำคัญ แต่สมมติว่าเป็นอาร์เรย์ของอักขระ ASCII ความยาวไบต์ที่แสดงถึงสิ่งที่ปรากฏบนหน้าจอในปัจจุบันและอาร์เรย์ที่แสดงถึงบรรทัดสุดท้ายของอินพุตที่ป้อนโดยผู้ใช้ในรูปแบบ pseudocode เราได้กำหนดฟังก์ชันบางอย่างที่ใช้สถานะคอนโซลแก้ไขและส่งคืนสถานะคอนโซลใหม่
consolestate MyConsole = new consolestate;
ดังนั้นในการเขียนโปรแกรมคอนโซล แต่ในลักษณะการทำงานที่บริสุทธิ์คุณจะต้องเรียกฟังก์ชั่นจำนวนมากภายในแต่ละอื่น ๆ
consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");
การเขียนโปรแกรมด้วยวิธีนี้จะรักษารูปแบบการทำงานที่ "บริสุทธิ์" ในขณะที่บังคับให้การเปลี่ยนแปลงคอนโซลเกิดขึ้นในลำดับที่เฉพาะเจาะจง แต่เราอาจต้องการดำเนินการมากกว่าสองสามครั้งในตัวอย่างด้านบน ฟังก์ชั่นการทำรังในลักษณะนั้นจะเริ่มไม่เป็นหลัก สิ่งที่เราต้องการคือโค้ดที่ทำสิ่งเดียวกับด้านบน แต่เขียนเป็นแบบนี้:
consolestate FinalConsole = myconsole:
print("Hello, what's your name?"):
input():
print("hello, %inputbuffer%!");
นี่จะเป็นวิธีที่สะดวกกว่าในการเขียน เราจะทำเช่นนั้นได้อย่างไร
Monad คืออะไร
เมื่อคุณมีประเภท (เช่นconsolestate
) ที่คุณกำหนดพร้อมกับฟังก์ชั่นมากมายที่ออกแบบมาเพื่อใช้กับประเภทนั้นคุณสามารถเปลี่ยนแพคเกจทั้งหมดของสิ่งเหล่านี้ให้กลายเป็น "monad" โดยการกำหนดโอเปอเรเตอร์เช่น:
(ผูก) ที่อัตโนมัติ ฟีดส่งคืนค่าทางด้านซ้ายของมันเป็นพารามิเตอร์ฟังก์ชันทางด้านขวาและlift
ผู้ดำเนินการที่เปลี่ยนฟังก์ชั่นปกติเป็นฟังก์ชั่นที่ทำงานกับตัวดำเนินการผูกชนิดนั้นโดยเฉพาะ
Monad ดำเนินการอย่างไร
ดูคำตอบอื่น ๆ ที่ค่อนข้างอิสระในการดูรายละเอียด
หลังจากให้คำตอบสำหรับคำถามนี้เมื่อไม่กี่ปีที่ผ่านมาฉันเชื่อว่าฉันสามารถปรับปรุงและทำให้การตอบกลับง่ายขึ้นด้วย ...
Monad เป็นเทคนิคการจัดองค์ประกอบของฟังก์ชั่นที่ทำให้การbind
ประมวลผลภายนอกสำหรับบางสถานการณ์จำลองโดยใช้ฟังก์ชั่นการเขียนเพื่อนำเข้ากระบวนการก่อนระหว่างองค์ประกอบ
ในการจัดองค์ประกอบปกติฟังก์ชั่นcompose (>>)
จะใช้ในการใช้ฟังก์ชั่นที่ประกอบขึ้นกับผลลัพธ์ของรุ่นก่อนตามลำดับ จำเป็นต้องใช้ฟังก์ชั่นที่กำลังประกอบเพื่อจัดการกับทุกสถานการณ์ของอินพุต
(x -> y) >> (y -> z)
การออกแบบนี้สามารถปรับปรุงได้โดยการปรับโครงสร้างอินพุทเพื่อให้สถานะที่เกี่ยวข้องถูกสอบปากคำได้ง่ายขึ้น ดังนั้นแทนที่จะเป็นเพียงy
ค่าสามารถกลายเป็นMb
เช่น(is_OK, b)
ถ้าy
รวมความคิดของความถูกต้อง
ตัวอย่างเช่นเมื่อใส่เป็นเพียงอาจจะเป็นตัวเลขแทนที่จะกลับสตริงซึ่งสามารถมีหน้าที่มีหมายเลขหรือไม่คุณสามารถปรับโครงสร้างชนิดเข้าที่bool
แสดงให้เห็นการปรากฏตัวของตัวเลขที่ถูกต้องและตัวเลขใน tuple bool * float
เช่น, ตอนนี้ฟังก์ชั่นที่สงบจะไม่จำเป็นต้องแยกวิเคราะห์สตริงอินพุตอีกต่อไปเพื่อพิจารณาว่ามีตัวเลขอยู่หรือไม่ แต่สามารถตรวจสอบbool
ส่วนของ tuple ได้
(Ma -> Mb) >> (Mb -> Mc)
ที่นี่อีกครั้งการจัดองค์ประกอบที่เกิดขึ้นตามธรรมชาติด้วยcompose
ดังนั้นแต่ละฟังก์ชั่นจะต้องจัดการกับทุกสถานการณ์ของการป้อนข้อมูลของแต่ละบุคคล
อย่างไรก็ตามจะเกิดอะไรขึ้นถ้าเราสามารถทำให้ความพยายามในการซักถามนอกช่วงเวลานั้นเป็นเรื่องที่ต้องทำเป็นประจำ ตัวอย่างเช่นสิ่งที่ถ้าโปรแกรมของเราไม่ทำอะไรเลยเมื่อป้อนข้อมูลที่ไม่ได้ตกลงกับในเมื่อเป็นis_OK
false
ถ้าทำแล้วฟังก์ชั่นที่สงบจะไม่จำเป็นต้องจัดการกับสถานการณ์นั้นด้วยตัวเองทำให้รหัสของพวกเขาง่ายขึ้นและทำให้เกิดการใช้ซ้ำอีกระดับ
เพื่อให้บรรลุ externalization นี้เราสามารถใช้ฟังก์ชั่นbind (>>=)
เพื่อดำเนินการแทนcomposition
compose
เช่นแทนที่จะโอนค่าจากการส่งออกของหนึ่งฟังก์ชั่นการป้อนข้อมูลของผู้อื่นBind
จะตรวจสอบM
ในส่วนของและตัดสินใจว่าและวิธีการใช้ฟังก์ชั่นประกอบไปMa
a
แน่นอนว่าฟังก์ชั่นbind
นั้นจะถูกกำหนดไว้โดยเฉพาะสำหรับเราโดยเฉพาะM
เพื่อให้สามารถตรวจสอบโครงสร้างและดำเนินการแอปพลิเคชันประเภทใดก็ได้ที่เราต้องการ อย่างไรก็ตามสิ่งที่a
สามารถเป็นได้ตั้งแต่bind
เพียงส่งผ่านไม่ได้รับการa
ตรวจสอบไปยังฟังก์ชั่นที่แต่งขึ้นเมื่อมันกำหนดแอพพลิเคชั่นที่จำเป็น นอกจากนี้ฟังก์ชั่นที่แต่งเองไม่จำเป็นต้องจัดการกับM
ส่วนของโครงสร้างอินพุตอย่างใดอย่างหนึ่งทำให้พวกเขาง่ายขึ้น ดังนั้น ...
(a -> Mb) >>= (b -> Mc)
หรือมากกว่ารัดกุม Mb >>= (b -> Mc)
กล่าวโดยย่อคือ monad ส่งสัญญาณภายนอกและให้การทำงานมาตรฐานรอบการจัดการกับสถานการณ์อินพุตบางอย่างเมื่ออินพุตถูกออกแบบมาเพื่อให้เปิดเผยอย่างเพียงพอ การออกแบบนี้เป็นshell and content
แบบจำลองที่เชลล์มีข้อมูลที่เกี่ยวข้องกับแอปพลิเคชันของฟังก์ชั่นที่ประกอบด้วยและถูกสอบปากคำโดยและยังคงใช้ได้เฉพาะกับbind
ฟังก์ชั่น
ดังนั้น monad คือสามสิ่ง:
M
เปลือกสำหรับการถือครอง Monad ข้อมูลที่เกี่ยวข้อง bind
ฟังก์ชั่นการใช้งานที่จะทำให้การใช้ข้อมูลเปลือกในการประยุกต์ใช้ฟังก์ชั่นประกอบกับค่าเนื้อหา (s) ที่พบภายในเปลือกและ a -> Mb
สร้างผลลัพธ์ที่มีข้อมูลการจัดการแบบ monadicโดยทั่วไปแล้วอินพุตไปยังฟังก์ชันมีข้อ จำกัด มากกว่าเอาต์พุตซึ่งอาจรวมถึงสิ่งต่าง ๆ เช่นเงื่อนไขข้อผิดพลาด ดังนั้นMb
โครงสร้างผลลัพธ์จึงมีประโยชน์มาก 0
ตัวอย่างเช่นผู้ประกอบการในส่วนที่ไม่ได้กลับจำนวนเมื่อหารเป็น
นอกจากนี้monad
s อาจรวมถึงฟังก์ชั่นการตัดที่ห่อค่า, a
เป็นประเภท monadic Ma
, และฟังก์ชั่นทั่วไปa -> b
, ลงในฟังก์ชั่น monadic a -> Mb
โดยการห่อผลลัพธ์ของพวกเขาหลังจากการประยุกต์ใช้ แน่นอนเช่นฟังก์ชั่นห่อดังกล่าวมีเฉพาะbind
M
ตัวอย่าง:
let return a = [a]
let lift f a = return (f a)
การออกแบบbind
ฟังก์ชั่นจะสันนิษฐานโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปแบบและฟังก์ชั่นที่บริสุทธิ์สิ่งอื่น ๆ ที่ซับซ้อนและไม่สามารถรับประกันได้ ดังนั้นจึงมีกฎหมาย monadic:
ได้รับ ...
M_
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)
จากนั้น ...
Left Identity : (return a) >>= f === f a
Right Identity : Ma >>= return === Ma
Associative : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)
Associativity
หมายความว่าbind
รักษาลำดับของการประเมินโดยไม่คำนึงว่าbind
จะใช้เมื่อใด นั่นคือในความหมายของAssociativity
ข้างต้นมีผลบังคับใช้การประเมินผลในช่วงต้นของวงเล็บbinding
ของf
และg
จะส่งผลให้มีฟังก์ชั่นที่คาดหวังในการสั่งซื้อให้เสร็จสมบูรณ์Ma
bind
ดังนั้นการประเมินMa
จะต้องพิจารณาก่อนที่ความคุ้มค่าจะกลายเป็นนำไปใช้และผลที่ในการเปิดนำไปใช้กับf
g
Monad คือรูปแบบของ "ตัวดำเนินการพิมพ์" อย่างมีประสิทธิภาพ มันจะทำสามสิ่ง ก่อนอื่นมันจะ "ตัด" (หรือแปลงเป็นอย่างอื่น) ค่าประเภทหนึ่งเป็นประเภทอื่น (โดยทั่วไปเรียกว่า "ประเภท monadic") ประการที่สองมันจะทำให้การดำเนินงานทั้งหมด (หรือฟังก์ชั่น) สามารถใช้ได้กับประเภทพื้นฐานที่มีอยู่ในประเภท monadic ในที่สุดมันก็จะให้การสนับสนุนสำหรับการรวมตัวของมันเองกับ Monad อื่นเพื่อผลิตคอมโพสิต Monad
"บางที monad" เป็นหลักเทียบเท่า "ประเภท nullable" ใน Visual Basic / C # ใช้ชนิด "T" ที่ไม่มีค่า null และแปลงเป็น "Nullable <T>" จากนั้นกำหนดว่าตัวดำเนินการไบนารีทั้งหมดหมายถึงอะไรใน Nullable <T>
ผลข้างเคียงจะถูกแสดงพร้อม ๆ กัน มีการสร้างโครงสร้างที่เก็บคำอธิบายของผลข้างเคียงพร้อมกับค่าส่งคืนของฟังก์ชัน การดำเนินการ "ยก" แล้วคัดลอกผลข้างเคียงเป็นค่าที่ถูกส่งผ่านระหว่างฟังก์ชั่น
พวกเขาถูกเรียกว่า "monads" แทนที่จะเป็นชื่อที่เข้าใจง่ายกว่าของ "ตัวดำเนินการพิมพ์" ด้วยเหตุผลหลายประการ:
(ดูคำตอบได้ที่Monad คืออะไร )
แรงจูงใจที่ดีต่อ Monads คือ sigfpe (Dan Piponi) ที่คุณสามารถประดิษฐ์ Monads ได้! (และบางทีคุณอาจมีอยู่แล้ว) มีบทเรียนอื่น ๆ มากมายของ monadซึ่งหลายคนพยายามที่จะอธิบาย monads ใน "คำศัพท์ง่าย ๆ " โดยใช้การเปรียบเทียบที่ต่างกัน: นี่คือความผิดพลาดของการสอน monad ; หลีกเลี่ยงพวกเขา
ดังที่ DR MacIver บอกไว้ในบอกเราว่าทำไมภาษาของคุณถึงแย่มาก :
ดังนั้นสิ่งที่ฉันเกลียดเกี่ยวกับ Haskell:
เริ่มจากสิ่งที่ชัดเจน บทเรียน Monad ไม่ไม่ใช่พระ แบบฝึกหัดเฉพาะ พวกเขาไม่มีที่สิ้นสุดพระเจ้าทรงเกินเลยและน่ารัก นอกจากนี้ฉันไม่เคยเห็นหลักฐานที่น่าเชื่อถือใด ๆ ที่พวกเขาช่วยได้จริง อ่านคำจำกัดความของคลาสเขียนโค้ดบางส่วนเอาชนะชื่อที่น่ากลัว
คุณบอกว่าคุณเข้าใจว่าบางที monad? ดีคุณกำลังไป เพียงเริ่มใช้ monads อื่นและไม่ช้าก็เร็วคุณจะเข้าใจว่า monads ทั่วไป
[หากคุณมุ่งเน้นทางคณิตศาสตร์คุณอาจต้องการละเว้นบทเรียนจำนวนมากและเรียนรู้คำจำกัดความหรือติดตามการบรรยายในหมวดหมู่ทฤษฎี :) ส่วนหลักของคำนิยามคือ Monad M เกี่ยวข้องกับ "ตัวสร้างประเภท" ที่กำหนดสำหรับแต่ละ ประเภท "T" ที่มีอยู่เดิมเป็น "MT" ชนิดใหม่และบางวิธีในการย้อนกลับไปมาระหว่างประเภท "ปกติ" และประเภท "M"]
นอกจากนี้ที่น่าแปลกใจมากพอที่หนึ่งของการเปิดตัวที่ดีที่สุดเพื่อ monads เป็นจริงหนึ่งในเอกสารทางวิชาการในช่วงต้นแนะนำ monads ฟิลิป Wadler ของMonads สำหรับการเขียนโปรแกรมการทำงาน จริง ๆ แล้วมันมีตัวอย่างที่เป็นประโยชน์และไม่เป็นแรงจูงใจซึ่งแตกต่างจากบทช่วยสอนที่ประดิษฐ์ขึ้นมากมาย
Monads คือการควบคุมโฟลว์ของชนิดข้อมูลนามธรรมให้กับข้อมูล
กล่าวอีกนัยหนึ่งนักพัฒนาหลายคนคุ้นเคยกับแนวคิดชุดรายการพจนานุกรม (หรือแฮชหรือแผนที่) และต้นไม้ ภายในชนิดข้อมูลเหล่านั้นมีกรณีพิเศษมากมาย (เช่น InsertionOrderPreservingIdentityHashMap)
อย่างไรก็ตามเมื่อเผชิญหน้ากับโปรแกรม "โฟลว์" นักพัฒนาหลายคนยังไม่ได้สัมผัสกับสิ่งก่อสร้างอื่น ๆ อีกมากมายกว่าถ้าเปลี่ยน / เคสทำในขณะที่ goto (grr) และ (อาจ) การปิด
ดังนั้น monad จึงเป็นโครงสร้างการควบคุมการไหล วลีที่ดีกว่าสำหรับแทนที่ monad คือ 'ประเภทการควบคุม'
ดังนั้น monad จึงมีช่องสำหรับควบคุมตรรกะหรือคำสั่งหรือฟังก์ชั่นซึ่งเทียบเท่ากับโครงสร้างข้อมูลที่จะกล่าวว่าโครงสร้างข้อมูลบางอย่างช่วยให้คุณสามารถเพิ่มข้อมูลและลบออกได้
ตัวอย่างเช่น "ถ้า" monad:
if( clause ) then block
ที่ง่ายที่สุดมีสองช่องคือส่วนคำสั่งและบล็อก โดยif
ปกติ Monad จะถูกสร้างขึ้นเพื่อประเมินผลลัพธ์ของประโยคและหากไม่ใช่เท็จให้ประเมินบล็อก นักพัฒนาหลายคนไม่ได้รับการแนะนำให้รู้จักกับพระเมื่อพวกเขาเรียนรู้ 'ถ้า' และไม่จำเป็นต้องเข้าใจพระในการเขียนตรรกะที่มีประสิทธิภาพ
Monads อาจมีความซับซ้อนมากขึ้นเช่นเดียวกับที่โครงสร้างข้อมูลอาจมีความซับซ้อนมากขึ้น แต่มี monad หลายประเภทที่อาจมีความหมายคล้ายกัน แต่มีการใช้งานและไวยากรณ์ที่แตกต่างกัน
แน่นอนในลักษณะเดียวกับที่โครงสร้างข้อมูลอาจถูกทำซ้ำหรือข้ามไปอาจมีการประเมิน monads
คอมไพเลอร์อาจมีหรือไม่มีการสนับสนุนสำหรับพระที่ผู้ใช้กำหนด Haskell ไม่แน่นอน Ioke มีความสามารถคล้ายกันถึงแม้ว่าคำว่า monad ไม่ได้ใช้ในภาษา
บทช่วยสอน Monad ที่ฉันชอบ:
http://www.haskell.org/haskellwiki/All_About_Monads
(จากจำนวน 170,000 ครั้งในการค้นหาโดย Google สำหรับ "monad tutorial"!)
@Stu: จุดของ monads คือการอนุญาตให้คุณเพิ่ม (โดยปกติ) ซีแมนทิกส์ตามลำดับไปยังโค้ดที่บริสุทธิ์; คุณสามารถเขียน monads (โดยใช้ Monad Transformers) และรับซีแมนทิครวมที่น่าสนใจและซับซ้อนเช่นการแยกวิเคราะห์การจัดการข้อผิดพลาดสถานะที่แชร์และการบันทึกเป็นต้น ทั้งหมดนี้เป็นไปได้ในรหัสบริสุทธิ์ monads อนุญาตให้คุณนำนามธรรมออกไปและนำกลับมาใช้ใหม่ในไลบรารีแบบแยกส่วน (ดีเสมอในการเขียนโปรแกรม) รวมถึงการให้ไวยากรณ์ที่สะดวกสบาย
Haskell มีโอเปอเรเตอร์การบรรทุกเกินพิกัด [1]: มันใช้คลาสคลาสมากในแบบที่อาจใช้อินเตอร์เฟสใน Java หรือ C # แต่ Haskell เพิ่งเกิดขึ้นเพื่ออนุญาตโทเค็นที่ไม่ใช่ตัวอักษรและตัวเลขเช่น + && และ> เป็นตัวระบุ infix มันเป็นแค่การใช้งานเกินพิกัดในทางที่คุณมองถ้าคุณหมายถึง "การใช้เครื่องหมายอัฒภาคมากเกินไป" [2] ดูเหมือนว่ามนต์ดำและการขอให้ "เกินเซมิโคลอน" (รูปภาพแฮ็กเกอร์ Perl ที่กล้าได้กล้าเสียได้รับความคิดนี้) แต่ประเด็นก็คือว่าถ้าไม่มีพระสงฆ์ก็ไม่มีอัฒภาคเนื่องจากโค้ดที่ใช้งานได้จริงไม่จำเป็นต้องอนุญาต
ทั้งหมดนี้ฟังดูซับซ้อนกว่าที่คุณต้องการ บทความของ sigfpe นั้นค่อนข้างดี แต่ใช้ Haskell เพื่ออธิบายซึ่งไม่สามารถทำลายปัญหาไก่และไข่ของการทำความเข้าใจ Haskell เพื่อ grok Monads และทำความเข้าใจ Monads เพื่อ grok Haskell
[1] นี่เป็นปัญหาแยกจาก monads แต่ monads ใช้คุณลักษณะการบรรทุกเกินพิกัดของ Haskell
[2] นี่ก็เป็นเรื่องที่ซับซ้อนเพราะผู้ใช้งานสำหรับการกระทำแบบ monadic คือ >> = (ออกเสียงว่า "ผูก") แต่มีน้ำตาลประโยค ("ทำ") ที่ให้คุณใช้วงเล็บปีกกาและเครื่องหมายอัฒภาคและ / หรือการเยื้องและการขึ้นบรรทัดใหม่
ฉันคิดถึง Monads ในวิธีที่ต่างออกไปเมื่อเร็ว ๆ นี้ ฉันคิดว่าพวกเขาจะสรุปลำดับการดำเนินการในรูปแบบทางคณิตศาสตร์ซึ่งทำให้เกิดความหลากหลายในรูปแบบใหม่
หากคุณกำลังใช้ภาษาที่จำเป็นและคุณเขียนนิพจน์ตามลำดับรหัส ALWAYS จะทำงานตามลำดับนั้นอย่างแน่นอน
และในกรณีง่าย ๆ เมื่อคุณใช้ monad รู้สึกเหมือนกัน - คุณกำหนดรายการของนิพจน์ที่เกิดขึ้นตามลำดับ ยกเว้นว่าขึ้นอยู่กับว่าคุณใช้ monad ใดรหัสของคุณอาจทำงานตามลำดับ (เช่นใน IO monad) ขนานกับหลาย ๆ รายการพร้อมกัน (เช่นใน List monad) มันอาจหยุดบางส่วน (เช่นในบางที monad) อาจหยุดชั่วคราวเพื่อดำเนินการต่อในภายหลัง (เช่นใน Resumption monad) อาจย้อนกลับและเริ่มต้นจากจุดเริ่มต้น (เช่นในธุรกรรม Monad) หรืออาจย้อนกลับบางส่วนเพื่อลองตัวเลือกอื่น ๆ (เช่นใน Logic monad) .
และเนื่องจาก monads นั้นเป็น polymorphic คุณจึงสามารถเรียกใช้รหัสเดียวกันใน monads ต่าง ๆ ได้ขึ้นอยู่กับความต้องการของคุณ
นอกจากนี้ในบางกรณีมันเป็นไปได้ที่จะรวม monads เข้าด้วยกัน (กับ monad transformers) เพื่อรับคุณสมบัติหลายอย่างในเวลาเดียวกัน
ฉันยังใหม่กับ monads แต่ฉันคิดว่าฉันจะแชร์ลิงก์ที่ฉันพบว่ารู้สึกดีมากที่จะอ่าน (พร้อมรูปภาพ !!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ monads-for-the-layman / (ไม่มีส่วนเกี่ยวข้อง)
โดยพื้นฐานแล้วแนวคิดที่อบอุ่นและคลุมเครือที่ฉันได้รับจากบทความคือแนวคิดที่ว่า monads นั้นเป็นอะแดปเตอร์ที่อนุญาตให้ฟังก์ชั่นที่แตกต่างกันในการทำงานแบบเรียงความคือสามารถสตริงฟังก์ชั่นที่หลากหลายและผสมกันได้ ประเภทและเช่น ดังนั้นฟังก์ชั่น BIND จะทำหน้าที่เก็บแอปเปิ้ลกับแอปเปิ้ลและส้มด้วยส้มเมื่อเราพยายามสร้างอะแดปเตอร์เหล่านี้ และฟังก์ชั่น LIFT รับผิดชอบการใช้งานฟังก์ชั่น "ระดับล่าง" และ "อัพเกรด" เพื่อใช้งานกับฟังก์ชั่น BIND และสามารถจัดองค์ประกอบได้เช่นกัน
ฉันหวังว่าฉันเข้าใจถูกต้องและที่สำคัญกว่านั้นหวังว่าบทความจะมีมุมมองที่ถูกต้องเกี่ยวกับพระ หากไม่มีอะไรอื่นบทความนี้ช่วยกระตุ้นความอยากอาหารของฉันให้เรียนรู้มากขึ้นเกี่ยวกับพระ
นอกจากคำตอบที่ดีเลิศข้างต้นฉันขอเสนอลิงก์ไปยังบทความต่อไปนี้ (โดย Patrick Thomson) ซึ่งอธิบาย monads โดยเกี่ยวข้องกับแนวคิดเกี่ยวกับไลบรารี JavaScript jQuery (และวิธีการใช้ "วิธีการผูกมัด" เพื่อจัดการ DOM) : jQuery เป็น Monad
เอกสาร jQueryตัวเองไม่ได้หมายถึงคำว่า "monad" แต่การเจรจาเกี่ยวกับการ "สร้างรูปแบบ" ซึ่งน่าจะเป็นที่คุ้นเคยมากขึ้น นี่ไม่ได้เปลี่ยนความจริงที่ว่าคุณมีพระที่เหมาะสมอยู่ที่นั่นบางทีอาจจะไม่ได้รู้ตัว
Monads Are อุปมาอุปมัยแต่เป็นนามธรรมที่เป็นประโยชน์ที่เกิดขึ้นจริงจากรูปแบบทั่วไปตามที่ Daniel Spiewak อธิบาย
Monad เป็นวิธีการรวมการคำนวณเข้าด้วยกันซึ่งใช้บริบทร่วมกัน มันเหมือนกับการสร้างเครือข่ายท่อ เมื่อสร้างเครือข่ายจะไม่มีข้อมูลไหลผ่าน แต่เมื่อฉันเสร็จ piecing บิตทั้งหมดพร้อมกับ 'ผูก' และ 'กลับ' จากนั้นฉันก็เรียกบางอย่างเช่นrunMyMonad monad data
และข้อมูลไหลผ่านท่อ
ในทางปฏิบัติ monad เป็นการใช้งานที่กำหนดเองของตัวดำเนินการจัดองค์ประกอบฟังก์ชั่นที่ดูแลผลข้างเคียงและอินพุตที่ไม่เข้ากันและค่าส่งคืน (สำหรับการผูกมัด)
หากฉันเข้าใจถูกต้อง IEnumerable นั้นมาจาก monads ฉันสงสัยว่าอาจเป็นมุมที่น่าสนใจสำหรับพวกเราจากโลก C # หรือไม่?
สำหรับสิ่งที่คุ้มค่าต่อไปนี้เป็นลิงก์สำหรับบทเรียนที่ช่วยฉัน (และไม่ฉันยังไม่เข้าใจว่า monads คืออะไร)
สองสิ่งที่ช่วยฉันได้ดีที่สุดเมื่อเรียนรู้ที่นั่นคือ:
บทที่ 8 "Parsers ฟังก์ชั่น" จากหนังสือเกรแฮมฮัตตันของการเขียนโปรแกรมใน Haskell อันนี้ไม่ได้พูดถึง monads เลยจริง ๆ แต่ถ้าคุณสามารถทำงานผ่านบทและเข้าใจทุกอย่างในนั้นโดยเฉพาะอย่างยิ่งการประเมินลำดับการดำเนินการผูกคุณจะเข้าใจ internals ของ monads คาดว่าสิ่งนี้จะต้องลองหลายครั้ง
กวดวิชาทั้งหมดเกี่ยวกับ Monads นี่เป็นตัวอย่างที่ดีหลายประการเกี่ยวกับการใช้งานและฉันต้องบอกว่าการเปรียบเทียบในภาคผนวกที่ฉันทำงานให้ฉัน
ดูเหมือนว่าจะเป็นสิ่งที่ทำให้มั่นใจได้ว่าการดำเนินการทั้งหมดที่กำหนดไว้ใน Monoid และประเภทที่สนับสนุนจะกลับมาเป็นประเภทที่ได้รับการสนับสนุนภายใน Monoid เช่นจำนวนใด ๆ + หมายเลขใด ๆ = จำนวนไม่มีข้อผิดพลาด
ในขณะที่แผนกรับเศษส่วนสองและส่งกลับเศษส่วนซึ่งกำหนดหารด้วยศูนย์เป็นไม่มีที่สิ้นสุดใน Haskell ยาย (ซึ่งเกิดขึ้นเป็นเศษส่วนเศษส่วน) ...
ไม่ว่าในกรณีใดปรากฏว่า Monads เป็นเพียงวิธีที่จะทำให้แน่ใจว่าสายการปฏิบัติการของคุณทำงานในลักษณะที่คาดการณ์ได้และฟังก์ชันที่อ้างว่าเป็น Num -> Num ซึ่งประกอบด้วยฟังก์ชันอื่นของ Num -> Num ที่เรียกด้วย x พูดยิงขีปนาวุธ
ในทางกลับกันถ้าเรามีฟังก์ชั่นที่ยิงขีปนาวุธเราสามารถเขียนมันด้วยฟังก์ชั่นอื่นที่ยิงขีปนาวุธเพราะเจตนาของเราชัดเจน - เราต้องการยิงขีปนาวุธ - แต่มันจะไม่พยายาม กำลังพิมพ์ "Hello World" ด้วยเหตุผลแปลก ๆ
ใน Haskell หลักคือ type IO () หรือ IO [()] การบิดเบือนนั้นแปลกและฉันจะไม่พูดถึงมัน แต่นี่คือสิ่งที่ฉันคิดว่าเกิดขึ้น:
ถ้าฉันมีหลักฉันต้องการให้มันเป็นลูกโซ่ของการกระทำเหตุผลที่ฉันเรียกใช้โปรแกรมก็คือการสร้างเอฟเฟกต์ - โดยปกติคือ IO ดังนั้นฉันสามารถโยงปฏิบัติการ IO เข้าด้วยกันเป็นหลักเพื่อที่จะ - ทำ IO ไม่มีอะไรอื่น
ถ้าฉันพยายามทำสิ่งที่ไม่ "คืน IO" โปรแกรมจะบ่นว่า chain ไม่ไหลหรือโดยทั่วไป "สิ่งนี้เกี่ยวข้องกับสิ่งที่เราพยายามทำ - การกระทำของ IO" ดูเหมือนว่าจะบังคับ โปรแกรมเมอร์เพื่อฝึกความคิดโดยไม่ต้องหลงทางและคิดเกี่ยวกับการยิงขีปนาวุธในขณะที่สร้างอัลกอริทึมสำหรับการคัดแยก - ซึ่งไม่ไหล
โดยทั่วไป Monads ดูเหมือนจะเป็นเคล็ดลับสำหรับคอมไพเลอร์ที่ "เฮ้คุณรู้ว่าฟังก์ชันนี้ที่ส่งกลับตัวเลขที่นี่มันไม่ได้ผลเสมอไปบางครั้งมันสามารถสร้างตัวเลขได้และบางครั้งก็ไม่มีอะไรเลย ใจ" หากคุณพยายามที่จะยืนยันการกระทำแบบ monadic การกระทำ monadic อาจทำหน้าที่เป็นข้อยกเว้นเวลารวบรวมว่า "เฮ้นี่ไม่ใช่ตัวเลขจริง ๆ นี่อาจเป็นตัวเลขได้ แต่คุณไม่สามารถสรุปได้ว่าทำอะไร เพื่อให้แน่ใจว่าการไหลนั้นเป็นที่ยอมรับ " ซึ่งป้องกันพฤติกรรมของโปรแกรมที่คาดเดาไม่ได้ - ในระดับที่ยุติธรรม
ดูเหมือนว่าพระจะไม่เกี่ยวกับความบริสุทธิ์หรือการควบคุม แต่เกี่ยวกับการรักษาเอกลักษณ์ของหมวดหมู่ซึ่งพฤติกรรมทั้งหมดสามารถคาดเดาและกำหนดได้หรือไม่ได้รวบรวม คุณไม่สามารถทำอะไรเมื่อคุณคาดว่าจะทำอะไรและคุณไม่สามารถทำอะไรถ้าคุณคาดว่าจะทำอะไร (มองเห็นได้)
เหตุผลที่ดีที่สุดที่ฉันนึกถึง Monads คือ - ไปดูขั้นตอนการทำงาน / รหัส OOP และคุณจะสังเกตได้ว่าคุณไม่รู้ว่าโปรแกรมจะเริ่มต้นหรือสิ้นสุดที่ใดก็ตามที่คุณเห็นคือการกระโดดและคณิตศาสตร์จำนวนมาก เวทมนตร์และขีปนาวุธ คุณจะไม่สามารถรักษามันไว้ได้และถ้าคุณทำได้คุณจะใช้เวลาค่อนข้างมากในการเขียนโปรแกรมทั้งหมดก่อนที่คุณจะสามารถเข้าใจส่วนใดส่วนหนึ่งของมันได้เนื่องจากส่วนย่อยในบริบทนี้ขึ้นอยู่กับ "ส่วน" ที่พึ่งพาซึ่งกันและกัน ของรหัสซึ่งรหัสจะได้รับการปรับปรุงให้มีความเกี่ยวข้องมากที่สุดเท่าที่จะเป็นไปได้สำหรับสัญญาด้านประสิทธิภาพ / ความสัมพันธ์ Monads นั้นเป็นรูปธรรมมากและถูกนิยามไว้อย่างดีโดยคำจำกัดความและทำให้แน่ใจว่าการไหลของโปรแกรมเป็นไปได้ในการวิเคราะห์และแยกส่วนที่ยากต่อการวิเคราะห์ - เนื่องจากพวกมันเป็น monads Monad ดูเหมือนจะเป็น " หรือทำลายจักรวาลหรือแม้กระทั่งเวลาบิดเบือน - เราไม่มีความคิดและไม่มีการรับประกันใด ๆ ว่าเป็นสิ่งที่มันเป็น monad รับประกันว่ามันคืออะไร ซึ่งมีประสิทธิภาพมาก หรือทำลายจักรวาลหรือแม้กระทั่งเวลาบิดเบือน - เราไม่มีความคิดและไม่มีการรับประกันใด ๆ ว่าเป็นสิ่งที่มันเป็น monad รับประกันว่ามันคืออะไร ซึ่งมีประสิทธิภาพมาก
ทุกสิ่งทุกอย่างใน "โลกแห่งความจริง" ดูเหมือนจะเป็นพระในแง่ที่ว่ามันถูกผูกมัดโดยกฎหมายที่สังเกตได้อย่างชัดเจนเพื่อป้องกันความสับสน นี่ไม่ได้หมายความว่าเราต้องเลียนแบบการดำเนินการทั้งหมดของวัตถุนี้เพื่อสร้างคลาส แต่เราสามารถพูดได้ว่า "a square is a square" ไม่มีอะไรเลยนอกจากเป็นรูปสี่เหลี่ยมจตุรัสไม่แม้แต่สี่เหลี่ยมผืนผ้าหรือวงกลมและ "a square มีพื้นที่ ขนาดความยาวของหนึ่งในนั้นคือมิติที่มีอยู่คูณด้วยตัวเองไม่ว่าคุณจะมีสี่เหลี่ยมจัตุรัสเท่าไหร่ถ้ามันเป็นสี่เหลี่ยมจัตุรัสในพื้นที่ 2 มิติพื้นที่นั้นไม่สามารถเป็นอะไรก็ได้ยกเว้นความยาวกำลังสอง เราไม่จำเป็นต้องยืนยันเพื่อให้แน่ใจว่าโลกของเราเป็นอย่างที่เป็นอยู่เราเพียงแค่ใช้ความหมายของความเป็นจริงเพื่อป้องกันไม่ให้โปรแกรมของเราหลุดออกนอกเส้นทาง
ฉันค่อนข้างรับประกันได้ว่าจะผิด แต่ฉันคิดว่านี่จะช่วยให้ใครบางคนออกไปที่นั่นดังนั้นหวังว่าจะช่วยให้ใครบางคน
ในบริบทของสกาล่าคุณจะพบว่าคำจำกัดความที่ง่ายที่สุดดังต่อไปนี้ โดยทั่วไป flatMap (หรือผูก) คือ 'เชื่อมโยง' และมีตัวตน
trait M[+A] {
def flatMap[B](f: A => M[B]): M[B] // AKA bind
// Pseudo Meta Code
def isValidMonad: Boolean = {
// for every parameter the following holds
def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))
// for every parameter X and x, there exists an id
// such that the following holds
def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
x.flatMap(id) == x
}
}
เช่น
// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)
// Observe these are identical. Since Option is a Monad
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)
scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)
// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)
// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)
scala> Some(7)
res214: Some[Int] = Some(7)
หมายเหตุอย่างเคร่งครัดพูดความหมายของการMonad ในการเขียนโปรแกรมการทำงานไม่ได้เป็นเช่นเดียวกับความหมายของการMonad ในหมวดหมู่ทฤษฎีซึ่งถูกกำหนดในการเปลี่ยนของและmap
flatten
แม้ว่าพวกเขาจะเทียบเท่าภายใต้การแมปบางอย่าง งานนำเสนอนี้ดีมาก: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category
คำตอบนี้เริ่มต้นด้วยตัวอย่างที่สร้างแรงบันดาลใจทำงานผ่านตัวอย่างมาจากตัวอย่างของ monad และกำหนด "monad" อย่างเป็นทางการ
พิจารณาทั้งสามฟังก์ชั่นใน pseudocode:
f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x) := <x, "">
f
รับคู่ที่สั่งซื้อของแบบฟอร์ม<x, messages>
และส่งกลับคู่ที่สั่งซื้อ มันปล่อยให้รายการแรกที่ไม่ถูกแตะต้องและผนวก"called f. "
เข้ากับรายการที่สอง g
เช่นเดียวกันกับ
คุณสามารถเขียนฟังก์ชั่นเหล่านี้และรับค่าดั้งเดิมของคุณพร้อมกับสตริงที่แสดงลำดับที่ฟังก์ชันถูกเรียกใช้ใน:
f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">
คุณไม่ชอบความจริงที่ว่าf
และg
รับผิดชอบต่อท้ายข้อความบันทึกของตัวเองไปยังข้อมูลการบันทึกก่อนหน้า (ลองนึกภาพเพื่อโต้แย้งว่าแทนที่จะต่อท้ายสตริงf
และg
ต้องใช้ตรรกะที่ซับซ้อนในรายการที่สองของคู่มันเป็นความเจ็บปวดที่จะทำซ้ำตรรกะที่ซับซ้อนในฟังก์ชั่นที่แตกต่างกันสองแบบหรือมากกว่า)
คุณชอบที่จะเขียนฟังก์ชั่นที่ง่ายขึ้น:
f(x) := <x, "called f. ">
g(x) := <x, "called g. ">
wrap(x) := <x, "">
แต่ดูสิ่งที่เกิดขึ้นเมื่อคุณเขียนมัน:
f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">
ปัญหาคือการส่งคู่เข้าสู่ฟังก์ชั่นไม่ได้ให้สิ่งที่คุณต้องการ แต่ถ้าคุณสามารถป้อนคู่เข้าสู่ฟังก์ชันได้:
feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">
อ่านfeed(f, m)
เป็น "ป้อนm
เข้าf
" การกินคู่<x, messages>
เข้าไปในฟังก์ชั่นf
คือการผ่าน x
เข้าไปf
รับ<y, message>
ออกจากและผลตอบแทนf
<y, messages message>
feed(f, <x, messages>) := let <y, message> = f(x)
in <y, messages message>
สังเกตว่าเกิดอะไรขึ้นเมื่อคุณทำสามสิ่งด้วยฟังก์ชันของคุณ:
ก่อน: ถ้าคุณตัดค่าแล้วป้อนคู่ผลลัพธ์ลงในฟังก์ชัน:
feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
in <y, "" message>
= let <y, message> = <x, "called f. ">
in <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)
นั่นคือเหมือนกับการส่งผ่านค่าลงในฟังก์ชัน
ประการที่สอง: หากคุณป้อนคู่ลงในwrap
:
feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
in <y, messages message>
= let <y, message> = <x, "">
in <y, messages message>
= <x, messages "">
= <x, messages>
ที่ไม่เปลี่ยนคู่
ประการที่สาม: หากคุณกำหนดฟังก์ชั่นที่ใช้x
และป้อนg(x)
เข้าf
:
h(x) := feed(f, g(x))
และป้อนคู่เข้าไป
feed(h, <x, messages>)
= let <y, message> = h(x)
in <y, messages message>
= let <y, message> = feed(f, g(x))
in <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
in <y, messages message>
= let <y, message> = let <z, msg> = f(x)
in <z, "called g. " msg>
in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
in <z, "called g. " msg>
in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))
นั่นคือเช่นเดียวกับการให้อาหารทั้งคู่เข้าg
และให้อาหารคู่ที่เกิดf
ขึ้น
คุณมี monad ส่วนใหญ่ ตอนนี้คุณเพียงแค่ต้องรู้เกี่ยวกับประเภทข้อมูลในโปรแกรมของคุณ
คุณค่าประเภท<x, "called f. ">
ใด ดีที่ขึ้นอยู่กับชนิดของมูลค่าx
เป็น หากx
เป็นประเภทt
แล้วคู่ของคุณมีค่าประเภท "คู่t
และสตริง" เรียกประเภทM t
นั้น
M
เป็นตัวสร้างประเภท: M
เพียงอย่างเดียวไม่ได้อ้างถึงประเภท แต่M _
อ้างอิงถึงประเภทเมื่อคุณกรอกข้อมูลในช่องว่างด้วยประเภท An M int
คือคู่ของ int และสตริง An M string
เป็นคู่ของสตริงและสตริง เป็นต้น
ขอแสดงความยินดีคุณสร้าง monad แล้ว!
อย่างเป็นทางการ monad คุณคือ <M, feed, wrap>
tuple
Monad เป็นสิ่งอันดับ<M, feed, wrap>
ที่:
M
เป็นตัวสร้างประเภทfeed
ยิง (ฟังก์ชั่นที่ใช้t
และส่งกลับM u
) และและส่งกลับM t
M u
wrap
ต้องใช้เวลาและการส่งกลับv
M v
t
, u
และv
มีสามประเภทที่อาจจะใช่หรือไม่เหมือนกัน Monad เป็นไปตามคุณสมบัติสามประการที่คุณพิสูจน์แล้วสำหรับ monad ของคุณโดยเฉพาะ:
การให้อาหารห่อt
เข้าไปในฟังก์ชั่นจะเหมือนกับการผ่านt
เข้าไปในฟังก์ชั่นที่ยังไม่ได้เปิด
อย่างเป็นทางการ: feed(f, wrap(x)) = f(x)
ให้อาหารM t
เข้าไปไม่ได้ทำอะไรไปwrap
M t
อย่างเป็นทางการ: feed(wrap, m) = m
ป้อนM t
(เรียกว่าm
) ลงในฟังก์ชั่นที่
t
เข้าไปg
M u
(เรียกว่าn
) จากg
n
เข้าf
เป็นเช่นเดียวกับ
m
เข้าไปg
n
จากg
n
เข้าไปf
เป็นทางการ: feed(h, m) = feed(f, feed(g, m))
ที่ไหนh(x) := feed(f, g(x))
โดยปกติfeed
จะเรียกว่าbind
(AKA >>=
ใน Haskell) และจะเรียกว่าwrap
return
ฉันจะพยายามอธิบายMonad
ในบริบทของ Haskell
ในการเขียนโปรแกรมเชิงฟังก์ชันองค์ประกอบของฟังก์ชั่นเป็นสิ่งสำคัญ ช่วยให้โปรแกรมของเราประกอบด้วยฟังก์ชั่นขนาดเล็กที่อ่านง่าย
สมมติว่าเรามีสองฟังก์ชั่น: และg :: Int -> String
f :: String -> Bool
เราสามารถทำได้(f . g) x
ซึ่งเหมือนกับค่าf (g x)
ที่x
อยู่ที่ไหนInt
เมื่อทำการจัดองค์ประกอบ / ใช้ผลลัพธ์ของฟังก์ชันหนึ่งกับอีกฟังก์ชันหนึ่งการจับคู่ประเภทเป็นสิ่งสำคัญ ในกรณีดังกล่าวข้างต้นประเภทของผลที่ได้กลับโดยจะต้องเป็นเช่นเดียวกับชนิดรับการยอมรับจากg
f
แต่บางครั้งค่าอยู่ในบริบทและสิ่งนี้ทำให้การจัดเรียงประเภทง่ายขึ้นเล็กน้อย (การมีค่าในบริบทมีประโยชน์มากตัวอย่างเช่นMaybe Int
ประเภทแสดงInt
ค่าที่อาจไม่มีอยู่IO String
ประเภทจะแสดงString
ค่าที่มีซึ่งเป็นผลจากการดำเนินการผลข้างเคียงบางอย่าง)
สมมติว่าตอนนี้เรามีและg1 :: Int -> Maybe String
และมีความคล้ายคลึงกับและตามลำดับf1 :: String -> Maybe Bool
g1
f1
g
f
เราทำไม่ได้(f1 . g1) x
หรือf1 (g1 x)
ที่ไหนx
มีInt
ค่า ประเภทของผลลัพธ์ที่ส่งคืนโดยg1
ไม่ใช่สิ่งที่f1
คาดหวัง
เราสามารถเขียนf
และg
กับ.
ผู้ประกอบการ แต่ตอนนี้เราไม่สามารถเขียนf1
และมีg1
.
ปัญหาคือเราไม่สามารถส่งค่าในบริบทไปยังฟังก์ชันที่คาดหวังว่าค่าที่ไม่ได้อยู่ในบริบทนั้นจะตรงไปตรงมา
มันจะไม่ดีถ้าเราแนะนำโอเปอเรเตอร์เพื่อเขียนg1
และf1
เช่นนั้นเราสามารถเขียนได้(f1 OPERATOR g1) x
? g1
ส่งคืนค่าในบริบท f1
ค่าที่จะถูกนำออกไปจากบริบทและนำไปใช้ และใช่เรามีผู้ประกอบการดังกล่าว <=<
มัน
นอกจากนี้เรายังมี>>=
โอเปอเรเตอร์ที่ทำสิ่งเดียวกันให้กับเราแม้ว่าจะอยู่ในรูปแบบที่แตกต่างกันเล็กน้อย
g1 x >>= f1
เราเขียน: g1 x
เป็นMaybe Int
ค่า >>=
ผู้ประกอบการจะช่วยให้ใช้เวลาที่Int
คุ้มค่าออกจาก "บางทีที่ไม่มี" f1
บริบทและนำมันไปใช้ ผลลัพธ์ของf1
ซึ่งก็คือ a Maybe Bool
จะเป็นผลลัพธ์ของการ>>=
ดำเนินการทั้งหมด
และในที่สุดทำไมมีMonad
ประโยชน์? เนื่องจากMonad
คลาสชนิดที่กำหนดตัว>>=
ดำเนินการเหมือนกับEq
คลาสชนิดที่กำหนด==
และ/=
ตัวดำเนินการ
ในการสรุปMonad
คลาสประเภทจะกำหนด>>=
โอเปอเรเตอร์ที่อนุญาตให้เราส่งค่าในบริบท (เราเรียกค่า monadic เหล่านี้) ไปยังฟังก์ชันที่ไม่คาดหวังค่าในบริบท บริบทจะได้รับการดูแล
หากมีสิ่งหนึ่งที่จะจำได้ว่านี่มันคือMonad
s ช่วยให้องค์ประกอบของฟังก์ชั่นที่เกี่ยวข้องกับค่าในบริบท
{-# LANGUAGE InstanceSigs #-}
newtype Id t = Id t
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
ผู้ประกอบการแอปพลิเคชัน$
ของฟังก์ชั่น
forall a b. a -> b
ถูกกำหนดตามมาตรฐาน
($) :: (a -> b) -> a -> b
f $ x = f x
infixr 0 $
ในแง่ของการประยุกต์ใช้ฟังก์ชั่นดั้งเดิม Haskell f x
( infixl 10
)
องค์ประกอบ.
ถูกกำหนดในแง่ของ$
เป็น
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x
infixr 9 .
และตอบสนองความเท่าเทียมกัน forall f g h.
f . id = f :: c -> d Right identity
id . g = g :: b -> c Left identity
(f . g) . h = f . (g . h) :: a -> d Associativity
.
มีการเชื่อมโยงและid
เป็นตัวตนทางขวาและซ้าย
ในการเขียนโปรแกรม monad เป็นตัวสร้างประเภท functor ที่มีอินสแตนซ์ของคลาสประเภท monad มีคำจำกัดความและการใช้งานที่เทียบเท่าหลายรูปแบบแต่ละแบบมีสัญชาติญาณที่แตกต่างกันเล็กน้อยเกี่ยวกับสิ่งที่เป็นนามธรรม
functor เป็นตัวสร้างf
ประเภทที่* -> *
มีประเภทของคลาสประเภท functor
{-# LANGUAGE KindSignatures #-}
class Functor (f :: * -> *) where
map :: (a -> b) -> (f a -> f b)
นอกเหนือจากโปรโตคอลประเภทบังคับใช้แบบคงที่แล้วอินสแตนซ์ของคลาสประเภท functor ต้องเป็นไปตามกฎหมายเกี่ยวกับพีชคณิตfunctor forall f g.
map id = id :: f t -> f t Identity
map f . map g = map (f . g) :: f a -> f c Composition / short cut fusion
การคำนวณ Functor มีประเภท
forall f t. Functor f => f t
คำนวณc r
ประกอบด้วยในผล r
ภายในบริบท c
ฟังก์ชั่นเอกนารีหรือลูกศร Kleisliมีประเภท
forall m a b. Functor m => a -> m b
ลูกศร Kleisi มีฟังก์ชั่นที่ใช้อาร์กิวเมนต์หนึ่งและกลับคำนวณเอกa
m b
Monads ถูกกำหนดตามแบบบัญญัติในแง่ของKleisli triple forall m. Functor m =>
(m, return, (=<<))
ดำเนินการเป็นคลาสประเภท
class Functor m => Monad m where
return :: t -> m t
(=<<) :: (a -> m b) -> m a -> m b
infixr 1 =<<
ตัวตน Kleisli return
เป็นลูกศร Kleisli ที่ส่งเสริมค่าในบริบทเอกt
ขยายหรือKleisli แอพลิเคชันได้ใช้ Kleisli ลูกศรผลของการคำนวณm
=<<
a -> m b
m a
องค์ประกอบของ Kleisli <=<
ถูกกำหนดในแง่ของการขยายเป็น
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x
infixr 1 <=<
<=<
ประกอบด้วยลูกศร Kleisli สองลูกโดยใช้ลูกศรซ้ายกับผลลัพธ์ของแอปพลิเคชันลูกศรขวา
อินสแตนซ์ของคลาสประเภท monad ต้องเป็นไปตามกฎของ monadที่ระบุไว้อย่างหรูหราที่สุดในแง่ขององค์ประกอบ Kleisli:forall f g h.
f <=< return = f :: c -> m d Right identity
return <=< g = g :: b -> m c Left identity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d Associativity
<=<
มีการเชื่อมโยงและreturn
เป็นตัวตนทางขวาและซ้าย
ประเภทข้อมูลประจำตัว
type Id t = t
เป็นฟังก์ชั่นเอกลักษณ์ในประเภท
Id :: * -> *
ตีความว่าเป็นนักแสดง
return :: t -> Id t
= id :: t -> t
(=<<) :: (a -> Id b) -> Id a -> Id b
= ($) :: (a -> b) -> a -> b
(<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
= (.) :: (b -> c) -> (a -> b) -> (a -> c)
ใน canonical Haskell จะมีการกำหนด monad เอกลักษณ์
newtype Id t = Id t
instance Functor Id where
map :: (a -> b) -> Id a -> Id b
map f (Id x) = Id (f x)
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
ประเภทตัวเลือก
data Maybe t = Nothing | Just t
เข้ารหัสการคำนวณMaybe t
ที่ไม่จำเป็นต้องให้ผลลัพธ์ผลลัพธ์t
การคำนวณที่อาจ“ ล้มเหลว” ตัวเลือก monad ถูกกำหนดไว้
instance Functor Maybe where
map :: (a -> b) -> (Maybe a -> Maybe b)
map f (Just x) = Just (f x)
map _ Nothing = Nothing
instance Monad Maybe where
return :: t -> Maybe t
return = Just
(=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
f =<< (Just x) = f x
_ =<< Nothing = Nothing
a -> Maybe b
ถูกนำไปใช้กับผลลัพธ์ก็ต่อเมื่อได้Maybe a
ผลลัพธ์
newtype Nat = Nat Int
ตัวเลขธรรมชาติสามารถเข้ารหัสเป็นจำนวนเต็มมากกว่าหรือเท่ากับศูนย์
toNat :: Int -> Maybe Nat
toNat i | i >= 0 = Just (Nat i)
| otherwise = Nothing
ตัวเลขธรรมชาติไม่ได้ถูกปิดภายใต้การลบ
(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)
infixl 6 -?
Monad ตัวเลือกครอบคลุมรูปแบบพื้นฐานของการจัดการข้อยกเว้น
(-? 20) <=< toNat :: Int -> Maybe Nat
รายการ monad เหนือประเภทรายการ
data [] t = [] | t : [t]
infixr 5 :
และการดำเนินการ monoid เสริมของมัน "ผนวก"
(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[] ++ ys = ys
infixr 5 ++
ถอดรหัสไม่เชิงเส้นคำนวณ[t]
ผลผลิตเป็นจำนวนธรรมชาติของผลลัพธ์0, 1, ...
t
instance Functor [] where
map :: (a -> b) -> ([a] -> [b])
map f (x : xs) = f x : map f xs
map _ [] = []
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> [a] -> [b]
f =<< (x : xs) = f x ++ (f =<< xs)
_ =<< [] = []
ส่วนต่อขยาย=<<
เชื่อม++
แสดงรายการทั้งหมด[b]
ที่เกิดจากการใช้งานf x
ของ Kleisli ลูกศรa -> [b]
เพื่อองค์ประกอบของการเป็นรายการผลเดียว[a]
[b]
ปล่อยให้ตัวหารที่เหมาะสมของจำนวนเต็มบวกn
เป็น
divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]
divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)
แล้วก็
forall n. let { f = f <=< divisors } in f n = []
ในการกำหนดระดับประเภท monad แทนการขยาย=<<
การใช้มาตรฐาน Haskell พลิกระบุผูก>>=
ประกอบการ
class Applicative m => Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(>>) :: forall a b. m a -> m b -> m b
m >> k = m >>= \ _ -> k
{-# INLINE (>>) #-}
return :: a -> m a
return = pure
เพื่อความเรียบง่ายคำอธิบายนี้ใช้ลำดับชั้นของคลาสชนิด
class Functor f
class Functor m => Monad m
ใน Haskell ลำดับชั้นมาตรฐานปัจจุบันคือ
class Functor f
class Functor p => Applicative p
class Applicative m => Monad m
เพราะไม่เพียง แต่จะเป็นนักแสดงทุกคนเท่านั้น แต่ผู้สมัครทุกคนเป็นนักแสดง
การใช้ list monad คือ pseudocode ที่จำเป็น
for a in (1, ..., 10)
for b in (1, ..., 10)
p <- a * b
if even(p)
yield p
ๆ แปลไปยังบล็อกที่ต้องทำ ,
do a <- [1 .. 10]
b <- [1 .. 10]
let p = a * b
guard (even p)
return p
เทียบเท่าเข้าใจ monad ,
[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]
และการแสดงออก
[1 .. 10] >>= (\ a ->
[1 .. 10] >>= (\ b ->
let p = a * b in
guard (even p) >> -- [ () | even p ] >>
return p
)
)
สัญกรณ์และความเข้าใจ monad เป็นน้ำตาลประโยคสำหรับการแสดงออกที่ซ้อนกันผูก ตัวดำเนินการเชื่อมโยงใช้สำหรับการโยงชื่อโลคัลของผลลัพธ์ monadic
let x = v in e = (\ x -> e) $ v = v & (\ x -> e)
do { r <- m; c } = (\ r -> c) =<< m = m >>= (\ r -> c)
ที่ไหน
(&) :: a -> (a -> b) -> b
(&) = flip ($)
infixl 0 &
กำหนดฟังก์ชั่นป้องกัน
guard :: Additive m => Bool -> m ()
guard True = return ()
guard False = fail
พื้นที่ที่หน่วยประเภทหรือ“ tuple ว่างเปล่า”
data () = ()
พระเสริมที่สนับสนุนทางเลือกและความล้มเหลวสามารถถูกแยกออกโดยใช้คลาสประเภท
class Monad m => Additive m where
fail :: m t
(<|>) :: m t -> m t -> m t
infixl 3 <|>
instance Additive Maybe where
fail = Nothing
Nothing <|> m = m
m <|> _ = m
instance Additive [] where
fail = []
(<|>) = (++)
ที่ไหนfail
และ<|>
รูปแบบ monoidforall k l m.
k <|> fail = k
fail <|> l = l
(k <|> l) <|> m = k <|> (l <|> m)
และfail
เป็นองค์ประกอบที่ดูดซับ / ทำลายศูนย์ของพระสงฆ์เสริม
_ =<< fail = fail
หากเข้า
guard (even p) >> return p
even p
เป็นจริงจากนั้นตัวสร้างจะสร้าง[()]
และตามนิยามของ>>
ฟังก์ชันค่าคงที่โลคัล
\ _ -> return p
()
ถูกนำไปใช้ผล หากเป็นเท็จระบบจะสร้างรายการของ monad fail
( []
) ซึ่งไม่ได้ผลสำหรับลูกศร Kleisli ที่จะนำไปใช้>>
ดังนั้นสิ่งนี้p
จะถูกข้ามไป
น่าอับอายพระใช้เพื่อเข้ารหัสการคำนวณ stateful
โปรเซสเซอร์รัฐเป็นฟังก์ชั่น
forall st t. st -> (t, st)
ที่เปลี่ยนรัฐและอัตราผลตอบแทนผลst
รัฐสามารถเป็นอะไรก็ได้ ไม่มีอะไร, ธง, นับ, อาเรย์, จัดการ, เครื่องจักร, โลกt
st
ชนิดของตัวประมวลผลสถานะมักเรียกว่า
type State st t = st -> (t, st)
รัฐโปรเซสเซอร์ monad เป็น* -> *
functor State st
ชนิด ลูกศร Kleisli ของตัวประมวลผลสถานะ monad เป็นฟังก์ชัน
forall st a b. a -> (State st) b
ใน canonical Haskell จะมีการกำหนดเวอร์ชันสันหลังยาวของตัวประมวลผลสถานะ monad
newtype State st t = State { stateProc :: st -> (t, st) }
instance Functor (State st) where
map :: (a -> b) -> ((State st) a -> (State st) b)
map f (State p) = State $ \ s0 -> let (x, s1) = p s0
in (f x, s1)
instance Monad (State st) where
return :: t -> (State st) t
return x = State $ \ s -> (x, s)
(=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
in stateProc (f x) s1
ตัวประมวลผลสถานะทำงานโดยการระบุสถานะเริ่มต้น:
run :: State st t -> st -> (t, st)
run = stateProc
eval :: State st t -> st -> t
eval = fst . run
exec :: State st t -> st -> st
exec = snd . run
การเข้าถึงของรัฐที่ให้บริการโดยพื้นฐานget
และput
วิธีการที่เป็นนามธรรมมากกว่าstateful monads:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Monad m => Stateful m st | m -> st where
get :: m st
put :: st -> m ()
m -> st
ประกาศการพึ่งพาการทำงานของประเภทรัฐst
ใน Monad m
; ที่State t
ยกตัวอย่างเช่นจะเป็นตัวกำหนดประเภทของรัฐที่จะต้องt
ไม่ซ้ำกัน
instance Stateful (State st) st where
get :: State st st
get = State $ \ s -> (s, s)
put :: st -> State st ()
put s = State $ \ _ -> ((), s)
ด้วยประเภทหน่วยที่ใช้แบบอะนาล็อกกับเป็นvoid
C
modify :: Stateful m st => (st -> st) -> m ()
modify f = do
s <- get
put (f s)
gets :: Stateful m st => (st -> t) -> m t
gets f = do
s <- get
return (f s)
gets
มักใช้กับอุปกรณ์บันทึกข้อมูล
สถานะ monad ที่เทียบเท่าของเธรดตัวแปร
let s0 = 34
s1 = (+ 1) s0
n = (* 12) s1
s2 = (+ 7) s1
in (show n, s2)
ที่s0 :: Int
มีความโปร่งใสเท่า ๆ กัน แต่มีความสง่างามและใช้งานได้จริง
(flip run) 34
(do
modify (+ 1)
n <- gets (* 12)
modify (+ 7)
return (show n)
)
modify (+ 1)
คือการคำนวณจากประเภทState Int ()
ยกเว้นของผลreturn ()
เทียบเท่ากับ
(flip run) 34
(modify (+ 1) >>
gets (* 12) >>= (\ n ->
modify (+ 7) >>
return (show n)
)
)
กฎของการเชื่อมโยง monad สามารถเขียนได้ในรูปของ >>=
forall m f g.
(m >>= f) >>= g = m >>= (\ x -> f x >>= g)
หรือ
do { do { do {
r1 <- do { x <- m; r0 <- m;
r0 <- m; = do { = r1 <- f r0;
f r0 r1 <- f x; g r1
}; g r1 }
g r1 }
} }
เช่นเดียวกับในการเขียนโปรแกรมเชิงแสดงออก (เช่นสนิม) คำสั่งสุดท้ายของบล็อกแสดงถึงผลผลิตของมัน ตัวดำเนินการเชื่อมโยงบางครั้งเรียกว่า "อัฒภาคแบบตั้งโปรแกรมได้"
โครงสร้างการควบคุมการทำซ้ำแบบดั้งเดิมจากการเขียนโปรแกรมความจำเป็นเชิงโครงสร้างจะถูกจำลองแบบ monadically
for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())
while :: Monad m => m Bool -> m t -> m ()
while c m = do
b <- c
if b then m >> while c m
else return ()
forever :: Monad m => m t
forever m = m >> forever m
data World
Monad state processor I / O โลกคือการประนีประนอม Haskell บริสุทธิ์และโลกแห่งความเป็นจริงของความหมายเชิงปฏิบัติการ denotative และความจำเป็นในการดำเนินงาน อะนาล็อกที่ใกล้เคียงกับการใช้งานจริงอย่างเข้มงวด:
type IO t = World -> (t, World)
การโต้ตอบจะช่วยอำนวยความสะดวกโดยไม่บริสุทธิ์ดั้งเดิม
getChar :: IO Char
putChar :: Char -> IO ()
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
hSetBuffering :: Handle -> BufferMode -> IO ()
hTell :: Handle -> IO Integer
. . . . . .
ความไม่บริสุทธิ์ของรหัสที่ใช้IO
primitives ถูกโพรโทคอลอย่างถาวรโดยระบบชนิด เพราะความบริสุทธิ์นั้นยอดเยี่ยมสิ่งที่เกิดขึ้นIO
อยู่ในIO
นั้น
unsafePerformIO :: IO t -> t
หรืออย่างน้อยควร
ลายเซ็นประเภทของโปรแกรม Haskell
main :: IO ()
main = putStrLn "Hello, World!"
ขยายเป็น
World -> ((), World)
ฟังก์ชั่นที่เปลี่ยนโลก
วัตถุประเภทการ whiches มี Haskell ชนิดและ whiches morphisms มีฟังก์ชั่นระหว่างประเภท Haskell คือ“ได้อย่างรวดเร็วและหลวม” Hask
หมวดหมู่
functor T
คือการทำแผนที่จากหมวดหมู่C
ไปที่ประเภทD
; สำหรับแต่ละวัตถุในC
วัตถุในD
Tobj : Obj(C) -> Obj(D)
f :: * -> *
และสำหรับแต่ละมอร์ฟิซึ่มส์ในC
มอร์ฟิซึ่มในD
Tmor : HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
map :: (a -> b) -> (f a -> f b)
ที่X
, มีวัตถุในY
เป็นระดับ homomorphismของ morphisms ทั้งหมดใน functor ต้องรักษาตัวตนซึ่มส์และองค์ประกอบที่“โครงสร้าง” ของในC
HomC(X, Y)
X -> Y
C
C
D
Tmor Tobj
T(id) = id : T(X) -> T(X) Identity
T(f) . T(g) = T(f . g) : T(X) -> T(Z) Composition
หมวด Kleisliของหมวดหมู่นี้C
จะได้รับโดย Kleisli สาม
<T, eta, _*>
ของ endofunctor
T : C -> C
( f
), มอร์ฟิซึ่มเอกลักษณ์eta
( return
) และผู้ดำเนินการส่วนขยาย*
( =<<
)
แต่ละมอร์ฟิซึ่ม Kleisli ใน Hask
f : X -> T(Y)
f :: a -> m b
โดยผู้ประกอบการส่วนขยาย
(_)* : Hom(X, T(Y)) -> Hom(T(X), T(Y))
(=<<) :: (a -> m b) -> (m a -> m b)
ได้รับ morphism ในHask
หมวดหมู่ของ Kleisli
f* : T(X) -> T(Y)
(f =<<) :: m a -> m b
องค์ประกอบในหมวดหมู่ Kleisli .T
นั้นได้รับในแง่ของการขยาย
f .T g = f* . g : X -> T(Z)
f <=< g = (f =<<) . g :: a -> m c
และสอดคล้องกับสัจพจน์หมวดหมู่
eta .T g = g : Y -> T(Z) Left identity
return <=< g = g :: b -> m c
f .T eta = f : Z -> T(U) Right identity
f <=< return = f :: c -> m d
(f .T g) .T h = f .T (g .T h) : X -> T(U) Associativity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d
ซึ่งใช้การแปลงสมมูล
eta .T g = g
eta* . g = g By definition of .T
eta* . g = id . g forall f. id . f = f
eta* = id forall f g h. f . h = g . h ==> f = g
(f .T g) .T h = f .T (g .T h)
(f* . g)* . h = f* . (g* . h) By definition of .T
(f* . g)* . h = f* . g* . h . is associative
(f* . g)* = f* . g* forall f g h. f . h = g . h ==> f = g
ในแง่ของการขยายจะได้รับบัญญัติ
eta* = id : T(X) -> T(X) Left identity
(return =<<) = id :: m t -> m t
f* . eta = f : Z -> T(U) Right identity
(f =<<) . return = f :: c -> m d
(f* . g)* = f* . g* : T(X) -> T(Z) Associativity
(((f =<<) . g) =<<) = (f =<<) . (g =<<) :: m a -> m c
Monads ยังสามารถกำหนดได้ในแง่ของการขยายไม่ Kleislian แต่การเปลี่ยนแปลงทางธรรมชาติmu
, join
การเขียนโปรแกรมที่เรียกว่าใน Monad ถูกกำหนดในแง่ของการmu
เป็นสามเท่าของหมวดหมู่C
ของ endofunctor
T : C -> C
f :: * -> *
และสองรูปแบบตามธรรมชาติ
eta : Id -> T
return :: t -> f t
mu : T . T -> T
join :: f (f t) -> f t
ความพึงพอใจเทียบเท่า
mu . T(mu) = mu . mu : T . T . T -> T . T Associativity
join . map join = join . join :: f (f (f t)) -> f t
mu . T(eta) = mu . eta = id : T -> T Identity
join . map return = join . return = id :: f t -> f t
คลาสประเภท monad ถูกกำหนดแล้ว
class Functor m => Monad m where
return :: t -> m t
join :: m (m t) -> m t
การmu
ใช้งานตามบัญญัติของ monad ตัวเลือก:
instance Monad Maybe where
return = Just
join (Just m) = m
join Nothing = Nothing
concat
ฟังก์ชั่น
concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat [] = []
เป็นjoin
ของรายการ monad
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> ([a] -> [b])
(f =<<) = concat . map f
การใช้งานของjoin
สามารถแปลได้จากรูปแบบการขยายโดยใช้เทียบเท่า
mu = id* : T . T -> T
join = (id =<<) :: m (m t) -> m t
การแปลแบบย้อนกลับจากmu
เป็นรูปแบบส่วนขยายได้รับจาก
f* = mu . T(f) : T(X) -> T(Y)
(f =<<) = join . map f :: m a -> m b
Philip Wadler: Monads สำหรับการเขียนโปรแกรมการทำงาน
Simon L Peyton Jones, Philip Wadler: การเขียนโปรแกรมการทำงานที่จำเป็น
โจนาธานเอ็มฮิลล์, คี ธ คล๊าร์ค: ความรู้เบื้องต้นเกี่ยวกับทฤษฎีประเภท monads ทฤษฎีประเภทและความสัมพันธ์ของพวกเขากับการเขียนโปรแกรมการทำงาน '
Eugenio Moggi: พัฒนาการของการคำนวณและ Monads
แต่ทำไมทฤษฎีควรให้นามธรรมใช้สำหรับการเขียนโปรแกรมใด ๆ
คำตอบนั้นง่าย: ในฐานะนักวิทยาศาสตร์คอมพิวเตอร์เราเห็นคุณค่าของความเป็นนามธรรม ! เมื่อเราออกแบบส่วนต่อประสานกับส่วนประกอบซอฟต์แวร์เราต้องการให้มันเปิดเผยน้อยที่สุดเกี่ยวกับการใช้งาน เราต้องการที่จะสามารถแทนที่การใช้งานด้วยทางเลือกมากมาย 'อินสแตนซ์' อื่น ๆ ของ 'แนวคิด' เดียวกัน เมื่อเราออกแบบอินเทอร์เฟซทั่วไปให้กับไลบรารีโปรแกรมจำนวนมากสิ่งสำคัญยิ่งกว่าคืออินเตอร์เฟสที่เราเลือกนั้นมีการใช้งานที่หลากหลาย มันเป็นความคิดทั่วไปของแนวคิด monad ที่เราให้คุณค่าอย่างมากมันเป็นเพราะทฤษฎีหมวดหมู่นั้นเป็นนามธรรมดังนั้นแนวคิดของมันจึงมีประโยชน์สำหรับการเขียนโปรแกรม
มันแทบจะไม่น่าแปลกใจเลยที่การวางหลักเกณฑ์ทั่วไปของพระที่เรานำเสนอไว้ด้านล่างนี้ยังมีความสัมพันธ์ใกล้ชิดกับทฤษฎีหมวดหมู่ แต่เราเน้นว่าจุดประสงค์ของเรานั้นใช้ได้จริงมากไม่ใช่เพื่อ 'ใช้ทฤษฎีหมวดหมู่' แต่เป็นการหาวิธีทั่วไปในการจัดโครงสร้างไลบรารี combinator เป็นเพียงความโชคดีของเราที่นักคณิตศาสตร์ได้ทำงานมามากมายสำหรับเรา!
จากGeneralising Monads ถึง Arrowsโดย John Hughes
สิ่งที่โลกต้องการคือการโพสต์บล็อก monad อื่น แต่ฉันคิดว่านี่เป็นประโยชน์ในการระบุ monads ที่มีอยู่ในป่า
ด้านบนเป็นเศษส่วนที่เรียกว่าสามเหลี่ยม Sierpinski ซึ่งเป็นเศษส่วนเดียวที่ฉันจำได้ เศษส่วนเป็นโครงสร้างที่คล้ายตัวเองเช่นสามเหลี่ยมด้านบนซึ่งชิ้นส่วนมีความคล้ายคลึงกับทั้งหมด (ในกรณีนี้ครึ่งหนึ่งของมาตราส่วนเท่ากับสามเหลี่ยมหลัก)
Monads เป็น fractals เมื่อกำหนดโครงสร้างข้อมูลแบบ monadic ค่าของมันจะสามารถสร้างเป็นค่าอื่นของโครงสร้างข้อมูลได้ นี่คือเหตุผลที่มีประโยชน์กับการเขียนโปรแกรมและนี่คือสาเหตุที่มันเกิดขึ้นในหลาย ๆ สถานการณ์
http://code.google.com/p/monad-tutorial/เป็นงานที่อยู่ระหว่างดำเนินการเพื่อตอบคำถามนี้อย่างแน่นอน
ให้ด้านล่าง{| a |m}
เป็นตัวแทนของข้อมูลแบบโมนาดิคบางส่วน ประเภทข้อมูลที่โฆษณาa
:
(I got an a!)
/
{| a |m}
ฟังก์ชั่น f
รู้วิธีสร้าง monad หากมีa
:
(Hi f! What should I be?)
/
(You?. Oh, you'll be /
that data there.) /
/ / (I got a b.)
| -------------- |
| / |
f a |
|--later-> {| b |m}
ที่นี่เราเห็นฟังก์ชั่นf
พยายามประเมิน monad แต่โดนตำหนิ
(Hmm, how do I get that a?)
o (Get lost buddy.
o Wrong type.)
o /
f {| a |m}
Funtion, f
ค้นหาวิธีในการแตกไฟล์a
>>=
โดยใช้
(Muaahaha. How you
like me now!?)
(Better.) \
| (Give me that a.)
(Fine, well ok.) |
\ |
{| a |m} >>= f
เล็ก ๆ น้อย ๆ f
รู้ Monad และ>>=
สมรู้ร่วมคิด
(Yah got an a for me?)
(Yeah, but hey |
listen. I got |
something to |
tell you first |
...) \ /
| /
{| a |m} >>= f
แต่พวกเขาพูดอะไรกันจริง ๆ ดีขึ้นอยู่กับ monad การพูด แต่เพียงผู้เดียวในนามธรรมมีการใช้งานที่ จำกัด คุณต้องมีประสบการณ์กับพระโดยเฉพาะเพื่อทำความเข้าใจ
ตัวอย่างเช่นชนิดข้อมูลอาจจะ
data Maybe a = Nothing | Just a
มีอินสแตนซ์ monad ซึ่งจะทำหน้าที่ดังต่อไปนี้ ...
ในกรณีที่เป็นกรณี Just a
(Yah what is it?)
(... hm? Oh, |
forget about it. |
Hey a, yr up.) |
\ |
(Evaluation \ |
time already? \ |
Hows my hair?) | |
| / |
| (It's |
| fine.) /
| / /
{| a |m} >>= f
แต่สำหรับกรณีของ Nothing
(Yah what is it?)
(... There |
is no a. ) |
| (No a?)
(No a.) |
| (Ok, I'll deal
| with this.)
\ |
\ (Hey f, get lost.)
\ | ( Where's my a?
\ | I evaluate a)
\ (Not any more |
\ you don't. |
| We're returning
| Nothing.) /
| | /
| | /
| | /
{| a |m} >>= f (I got a b.)
| (This is \
| such a \
| sham.) o o \
| o|
|--later-> {| b |m}
ดังนั้นบางที monad ให้การคำนวณดำเนินต่อไปหากมีการa
โฆษณาจริง แต่จะยกเลิกการคำนวณหากไม่มี ผลที่ตามมา f
แต่ยังคงเป็นชิ้นส่วนของข้อมูลเอกแม้ว่าจะไม่ได้การส่งออกของ ด้วยเหตุผลนี้อาจใช้ monad เพื่อแสดงถึงบริบทของความล้มเหลว
พระต่าง ๆ ประพฤติตนแตกต่างกัน รายการเป็นข้อมูลประเภทอื่นที่มีอินสแตนซ์ monadic พวกเขาทำตัวเหมือนดังต่อไปนี้:
(Ok, here's your a. Well, its
a bunch of them, actually.)
|
| (Thanks, no problem. Ok
| f, here you go, an a.)
| |
| | (Thank's. See
| | you later.)
| (Whoa. Hold up f, |
| I got another |
| a for you.) |
| | (What? No, sorry.
| | Can't do it. I
| | have my hands full
| | with all these "b"
| | I just made.)
| (I'll hold those, |
| you take this, and /
| come back for more /
| when you're done /
| and we'll do it /
| again.) /
\ | ( Uhhh. All right.)
\ | /
\ \ /
{| a |m} >>= f
ในกรณีนี้ฟังก์ชั่นรู้วิธีสร้างรายการจากอินพุต แต่ไม่รู้ว่าจะทำอย่างไรกับอินพุตพิเศษและรายการพิเศษ การผูก>>=
ช่วยf
ด้วยการรวมเอาท์พุทหลาย ๆ ฉันรวมตัวอย่างนี้แสดงให้เห็นว่าในขณะที่>>=
เป็นผู้รับผิดชอบสำหรับการสกัดก็ยังมีการเข้าถึงการส่งออกที่ถูกผูกไว้ในที่สุดa
f
อันที่จริงมันจะไม่แยกใด ๆa
เว้นแต่จะรู้ว่าผลลัพธ์ในที่สุดมีบริบทประเภทเดียวกัน
มีพระอื่น ๆ ที่ใช้แทนบริบทที่แตกต่างกัน นี่คือลักษณะของอีกไม่กี่ IO
monad ไม่จริงมีa
แต่ก็รู้ว่าผู้ชายและจะได้รับว่าa
สำหรับคุณ State st
monad มีที่ซ่อนความลับของst
ว่ามันจะส่งผ่านไปf
ใต้โต๊ะแม้ว่าเพิ่งมาขอf
monad คล้ายกับแม้ว่ามันเพียง แต่ช่วยให้ดูที่a
Reader r
State st
f
r
ประเด็นทั้งหมดนี้คือข้อมูลประเภทใดก็ตามที่ประกาศตัวเองว่าเป็น Monad กำลังประกาศบริบทบางอย่างรอบ ๆ การดึงค่าออกมาจาก monad กำไรที่ยิ่งใหญ่จากทั้งหมดนี้? มันง่ายพอที่จะรองรับการคำนวณด้วยบริบทบางอย่าง มันสามารถทำให้เกิดความยุ่งเหยิงอย่างไรก็ตามเมื่อคบกันหลาย ๆ การคำนวณที่รับภาระ การดำเนินการ monad ดูแลการแก้ไขการโต้ตอบของบริบทเพื่อให้โปรแกรมเมอร์ไม่จำเป็นต้อง
หมายเหตุการใช้ที่ลดระเบียบโดยโดยการบางอย่างของตนเองออกไปจาก>>=
f
นั่นคือในกรณีข้างต้นNothing
ตัวอย่างเช่นf
ไม่ได้รับการตัดสินใจว่าจะทำอย่างไรในกรณีของNothing
; >>=
มันเข้ารหัสใน นี่คือการแลกเปลี่ยน ถ้ามันเป็นสิ่งที่จำเป็นสำหรับf
การตัดสินใจว่าจะทำอย่างไรในกรณีของNothing
แล้วf
ควรจะได้รับฟังก์ชั่นจากไปMaybe a
Maybe b
ในกรณีนี้Maybe
การเป็น monad นั้นไม่เกี่ยวข้อง
อย่างไรก็ตามโปรดทราบว่าบางครั้งประเภทข้อมูลไม่ส่งออกมันเป็นคอนสตรัคเตอร์ (ดูที่คุณ IO) และถ้าเราต้องการทำงานกับค่าโฆษณาที่เรามีทางเลือกน้อย
Monad เป็นสิ่งที่ใช้ห่อหุ้มวัตถุที่เปลี่ยนสถานะ มักพบในภาษาที่ไม่อนุญาตให้คุณแก้ไขสถานะได้ (เช่น Haskell)
ตัวอย่างจะใช้สำหรับไฟล์ I / O
คุณจะสามารถใช้ monad สำหรับไฟล์ I / O เพื่อแยกลักษณะการเปลี่ยนสถานะเป็นเพียงรหัสที่ใช้ Monad รหัสใน Monad สามารถเพิกเฉยต่อสภาพการเปลี่ยนแปลงของโลกภายนอก Monad ได้อย่างมีประสิทธิภาพซึ่งทำให้ง่ายขึ้นมากที่จะให้เหตุผลเกี่ยวกับผลกระทบโดยรวมของโปรแกรมของคุณ