Monad คืออะไร


1414

เมื่อเร็ว ๆ นี้เมื่อดูที่ Haskell สิ่งที่จะเป็นคำอธิบายสั้น ๆ รวบรัดและเป็นประโยชน์เกี่ยวกับสิ่งที่เป็นหลัก monad คืออะไร?

ฉันพบว่าคำอธิบายส่วนใหญ่ที่ฉันเจอนั้นไม่สามารถเข้าถึงได้อย่างเป็นธรรมและขาดรายละเอียดในทางปฏิบัติ


12
Eric Lippert เขียนคำตอบสำหรับคำถามนี้ ( stackoverflow.com/questions/2704652/… ) ซึ่งเกิดจากปัญหาบางอย่างอาศัยอยู่ในหน้าแยกต่างหาก
P Shved

70
นี่คือบทนำใหม่โดยใช้ javascript - ฉันพบว่าอ่านง่ายมาก
Benjol



2
monad เป็นอาร์เรย์ของฟังก์ชันที่มีการดำเนินการตัวช่วย ดูคำตอบนี้
cibercitizen1

คำตอบ:


1059

อย่างแรก: คำว่า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

อีกตัวอย่างหนึ่งคือข้อยกเว้น: การใช้Errormonad การดำเนินการจะถูกล่ามโซ่ซึ่งจะถูกดำเนินการตามลำดับยกเว้นว่ามีข้อผิดพลาดเกิดขึ้นในกรณีที่ส่วนที่เหลือของโซ่ถูกทอดทิ้ง

ทั้งไวยากรณ์ 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 เมื่อได้รับการตอบกลับ

ในภาษาอื่น ๆ ส่วนใหญ่คุณจะต้องสร้างฟังก์ชั่นแยกต่างหากสำหรับบรรทัดที่จัดการกับการตอบสนอง asyncmonad สามารถ "แยก" บล็อกของตัวเองและเลื่อนการดำเนินการในช่วงครึ่งหลัง ( async {}ไวยากรณ์ระบุว่าการควบคุมโฟลว์ในบล็อกถูกกำหนดโดยasyncmonad)

พวกเขาทำงานอย่างไร

ดังนั้น 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


65
ในฐานะคนที่มีปัญหามากมายในการทำความเข้าใจกับพระสงฆ์ฉันสามารถพูดได้ว่าคำตอบนี้ช่วย .. ได้เล็กน้อย อย่างไรก็ตามยังมีบางสิ่งที่ฉันไม่เข้าใจ รายการทำความเข้าใจกับ Monad ในทางใด? มีตัวอย่างที่ขยายออกไปหรือไม่? อีกสิ่งหนึ่งที่ทำให้ฉันรำคาญใจเกี่ยวกับคำอธิบายของ Monad ส่วนใหญ่รวมถึงสิ่งนี้ - พวกเขาปนกันอยู่เรื่อย ๆ ว่า "Monad คืออะไร" กับ "monad ที่ดีคืออะไร?" และ "Monad มีการใช้งานอย่างไร" คุณเพิ่มฉลามนั้นเมื่อคุณเขียนว่า "Monad นั้นเป็นเพียงแค่ประเภทที่รองรับ >> = operator" ซึ่งเพียงแค่มีฉัน ...
เบรอตง

82
นอกจากนี้ฉันไม่เห็นด้วยกับข้อสรุปของคุณเกี่ยวกับสาเหตุที่ monads ยาก หากพระตนเองไม่ซับซ้อนคุณควรจะสามารถอธิบายสิ่งที่พวกเขาไม่ได้รับกระเป๋า ฉันไม่ต้องการทราบเกี่ยวกับการใช้งานเมื่อฉันถามคำถาม "Monad คืออะไร" ฉันต้องการทราบว่ามีความหมายอะไรที่ทำให้เกิดรอยขีดข่วน จนถึงตอนนี้ดูเหมือนว่าคำตอบคือ "เพราะผู้เขียนของ Haskell เป็น sadomasochists และตัดสินใจว่าคุณควรทำสิ่งที่ซับซ้อนอย่างโง่เขลาเพื่อให้ได้สิ่งที่เรียบง่ายดังนั้นคุณต้องเรียนรู้พระที่จะใช้ haskell ไม่ใช่เพราะพวกเขามีประโยชน์ ตัวเอง" ...
เบรอตง

69
แต่ .. ที่ไม่ถูกต้องได้ไหม ฉันคิดว่าพระสงฆ์เป็นเรื่องยากเพราะไม่มีใครสามารถเข้าใจวิธีการอธิบายได้โดยไม่ต้องสับสนกับรายละเอียดของการติดตั้ง ฉันหมายถึง .. รถโรงเรียนคืออะไร? มันเป็นแพลตฟอร์มโลหะที่มีอุปกรณ์อยู่ด้านหน้าซึ่งใช้ผลิตภัณฑ์ปิโตรเลียมที่ผ่านการกลั่นเพื่อขับในวงรอบบางลูกสูบโลหะซึ่งจะหมุนเพลาข้อเหวี่ยงที่ติดอยู่กับเกียร์บางตัวที่ขับล้อ ล้อมีถุงยางพองรอบตัวซึ่งเชื่อมต่อกับพื้นผิวแอสฟัลต์เพื่อทำให้เกิดการสะสมของที่นั่งที่จะเคลื่อนที่ไปข้างหน้า ที่นั่งก้าวไปข้างหน้าเพราะ ...
เบรอตง

130
ฉันอ่านทั้งหมดและยังไม่รู้ว่า monad คืออะไรนอกจากข้อเท็จจริงที่ว่ามันเป็นสิ่งที่โปรแกรมเมอร์ Haskell เข้าใจไม่ดีพอที่จะอธิบายได้ ตัวอย่างไม่ได้ช่วยอะไรมากนักเนื่องจากสิ่งเหล่านี้เป็นสิ่งที่ทำได้โดยไม่ต้อง monads และคำตอบนี้ไม่ได้ทำให้ชัดเจนว่า monads ทำให้พวกเขาง่ายขึ้นเพียงแค่สับสนเท่านั้น ส่วนหนึ่งของคำตอบนี้ที่ใกล้เคียงกับการเป็นประโยชน์ก็คือการนำน้ำตาล syntactic ของตัวอย่าง # 2 ออก ฉันบอกว่าเข้ามาใกล้เพราะนอกเหนือจากบรรทัดแรกการขยายตัวไม่ได้มีความคล้ายคลึงกับต้นฉบับจริง
ลอเรนซ์ Gonsalves

81
ปัญหาอีกประการหนึ่งที่ดูเหมือนว่าจะเกิดขึ้นเฉพาะกับคำอธิบายของพระสงฆ์ก็คือมันเขียนไว้ใน Haskell ฉันไม่ได้บอกว่า Haskell เป็นภาษาที่ไม่ดี - ฉันกำลังพูดว่าเป็นภาษาที่ไม่ดีสำหรับการอธิบายพระ ถ้าฉันรู้ว่า Haskell ฉันเข้าใจพระแล้วดังนั้นหากคุณต้องการอธิบายพระเริ่มต้นด้วยการใช้ภาษาที่คนที่ไม่รู้จักพระมีแนวโน้มที่จะเข้าใจ หากคุณต้องใช้ Haskell อย่าใช้น้ำตาลประโยคโดยใช้ภาษาย่อยที่เล็กที่สุดและง่ายที่สุดที่คุณสามารถทำได้และไม่เข้าใจ Haskell IO
Laurence Gonsalves

712

การอธิบายว่า "monad คืออะไร" ก็เหมือนกับการพูดว่า "ตัวเลขคืออะไร" เราใช้ตัวเลขตลอดเวลา แต่ลองจินตนาการว่าคุณได้พบกับคนที่ไม่รู้จักอะไรเกี่ยวกับตัวเลข วิธีห่าคุณจะอธิบายว่าตัวเลขจะอยู่ที่ไหน? และคุณจะเริ่มอธิบายได้อย่างไรว่าทำไมถึงมีประโยชน์

Monad คืออะไร คำตอบสั้น ๆ : มันเป็นวิธีที่เฉพาะเจาะจงของการผูกมัดการทำงานร่วมกัน

ในสาระสำคัญคุณกำลังเขียนขั้นตอนการดำเนินการและเชื่อมโยงพวกเขาเข้าด้วยกันกับ "ฟังก์ชั่นการผูก" (ใน Haskell มีชื่อ>>=) คุณสามารถเขียนการเรียกไปยังโอเปอเรเตอร์ bind ด้วยตัวคุณเองหรือคุณสามารถใช้ซินแท็กซ์น้ำตาลซึ่งทำให้คอมไพเลอร์แทรกการเรียกฟังก์ชันเหล่านั้นให้คุณ แต่อย่างใดแต่ละขั้นตอนจะถูกคั่นด้วยการเรียกไปยังฟังก์ชันการโยงนี้

ดังนั้นฟังก์ชั่นการผูกจึงเป็นเครื่องหมายอัฒภาค; มันแยกขั้นตอนในกระบวนการ หน้าที่ของฟังก์ชั่น bind คือการเอาท์พุทจากขั้นตอนก่อนหน้าและป้อนเข้าสู่ขั้นตอนถัดไป

นั่นไม่ฟังดูยากเกินไปใช่มั้ย แต่มีmonad มากกว่าหนึ่งชนิด ทำไม? อย่างไร?

ฟังก์ชั่นการผูกสามารถนำผลลัพธ์มาจากขั้นตอนเดียวและป้อนเข้าสู่ขั้นตอนถัดไป แต่ถ้านั่นคือ "ทั้งหมด" monad ทำ ... นั่นไม่ได้มีประโยชน์จริงๆ และนั่นเป็นสิ่งสำคัญที่จะต้องเข้าใจ: monad ที่มีประโยชน์ทำอย่างอื่นนอกเหนือจากการเป็น monad monad ที่มีประโยชน์ทุกคนมี "พลังพิเศษ" ซึ่งทำให้เป็นเอกลักษณ์

(Monad ที่ไม่ทำสิ่งใดเป็นพิเศษเรียกว่า "identity monad" แทนที่จะฟังก์ชั่นเอกลักษณ์นี้ดูเหมือนจะเป็นสิ่งที่ไร้จุดหมาย แต่กลับกลายเป็นว่าไม่ใช่ ... แต่นั่นเป็นอีกเรื่อง™)

โดยทั่วไปแต่ละ monad จะมีฟังก์ชั่นการผูกของตัวเอง และคุณสามารถเขียนฟังก์ชั่นการเชื่อมโยงเพื่อให้มันวนรอบสิ่งต่าง ๆ ระหว่างขั้นตอนการดำเนินการ ตัวอย่างเช่น:

  • หากแต่ละขั้นตอนส่งกลับตัวบ่งชี้ความสำเร็จ / ความล้มเหลวคุณสามารถผูกดำเนินการขั้นตอนถัดไปได้หากขั้นตอนก่อนหน้าประสบความสำเร็จ ด้วยวิธีนี้ขั้นตอนที่ล้มเหลวจะยกเลิกลำดับทั้งหมด "โดยอัตโนมัติ" โดยไม่มีการทดสอบตามเงื่อนไขจากคุณ ( ความล้มเหลว Monad )

  • การขยายแนวคิดนี้คุณสามารถใช้ "ข้อยกเว้น" ได้ ( Monad ข้อผิดพลาดหรือข้อยกเว้น Monad ) เนื่องจากคุณกำหนดด้วยตนเองแทนที่จะเป็นคุณลักษณะภาษาคุณจึงสามารถกำหนดวิธีการทำงานได้ (เช่นคุณอาจต้องการละเว้นข้อยกเว้นสองข้อแรกและยกเลิกเฉพาะเมื่อมีการโยนข้อยกเว้นที่สาม )

  • คุณสามารถทำให้แต่ละขั้นตอนส่งคืนผลลัพธ์ได้หลายรายการและมีฟังก์ชันผูกการวนซ้ำพวกเขาแต่ละรายการเข้าสู่ขั้นตอนถัดไปสำหรับคุณ ด้วยวิธีนี้คุณไม่จำเป็นต้องเขียนวนซ้ำไปทั่วทุกที่เมื่อจัดการกับผลลัพธ์หลายรายการ ฟังก์ชั่นผูก "อัตโนมัติ" จะทำทุกอย่างให้คุณ ( รายการ Monad )

  • เช่นเดียวกับการส่งผ่าน "ผลลัพธ์" จากขั้นตอนหนึ่งไปอีกขั้นคุณสามารถให้ฟังก์ชันการเชื่อมโยงส่งผ่านข้อมูลพิเศษได้เช่นกัน ข้อมูลนี้ไม่แสดงในซอร์สโค้ดของคุณ แต่คุณยังสามารถเข้าถึงได้จากทุกที่โดยไม่ต้องส่งต่อไปยังทุกฟังก์ชั่นด้วยตนเอง (ผู้อ่าน Monad )

  • คุณสามารถทำให้มันเพื่อให้ "ข้อมูลพิเศษ" สามารถถูกแทนที่ สิ่งนี้จะช่วยให้คุณจำลองการอัพเดทแบบทำลายได้โดยไม่ต้องทำการอัพเดทแบบทำลาย ( รัฐโมนาดและลูกพี่ลูกน้องนักเขียนโมนาด)

  • เนื่องจากคุณจำลองการอัปเดตที่มีการทำลายเท่านั้นคุณสามารถทำสิ่งต่าง ๆ ที่เป็นไปไม่ได้ด้วยการอัปเดตที่ทำลายล้างจริง ตัวอย่างเช่นคุณสามารถเลิกทำการอัปเดตครั้งล่าสุดหรือเปลี่ยนกลับเป็นเวอร์ชันเก่ากว่าได้

  • คุณสามารถสร้าง monad ที่สามารถหยุดการคำนวณได้เพื่อให้คุณสามารถหยุดโปรแกรมของคุณชั่วคราวเข้าไปในและคนจรจัดกับข้อมูลสถานะภายในแล้วดำเนินการต่อ

  • คุณสามารถใช้ "การสืบเนื่อง" เป็น monad สิ่งนี้ช่วยให้คุณสามารถทำลายจิตใจของผู้คน!

ทั้งหมดนี้และอื่น ๆ เป็นไปได้กับ monads แน่นอนว่าทั้งหมดนี้เป็นไปได้อย่างสมบูรณ์แบบโดยไม่ต้อง monads เกินไป มันง่ายขึ้นมากเมื่อใช้ monads


13
ฉันขอขอบคุณคำตอบของคุณ - โดยเฉพาะอย่างยิ่งสัมปทานขั้นสุดท้ายที่แน่นอนว่าทั้งหมดนี้เป็นไปได้เช่นกันโดยไม่มีพระ สิ่งหนึ่งที่ต้องทำคือว่าส่วนใหญ่มันง่ายกว่า monads แต่ก็ไม่ได้มีประสิทธิภาพเท่าที่ทำโดยที่ไม่มีพวกเขา เมื่อคุณจำเป็นต้องมีส่วนเกี่ยวข้องกับหม้อแปลงการเพิ่มระดับของการเรียกใช้ฟังก์ชัน (และวัตถุฟังก์ชันที่สร้างขึ้น) มีค่าใช้จ่ายที่ยากต่อการมองเห็นและควบคุมทำให้มองไม่เห็นด้วยไวยากรณ์ที่ฉลาด
seh

1
อย่างน้อยใน Haskell ส่วนใหญ่ค่าโสหุ้ยของพระจะถูกเอาออกไปด้วยเครื่องมือเพิ่มประสิทธิภาพ ดังนั้น "ต้นทุน" ที่แท้จริงเพียงอย่างเดียวจึงจำเป็นต้องใช้พลังสมอง (นี่ไม่สำคัญหาก "การบำรุงรักษา" เป็นสิ่งที่คุณใส่ใจ) แต่โดยปกติแล้วพระสงฆ์จะทำให้สิ่งต่าง ๆง่ายขึ้นไม่ใช่เรื่องยาก (ไม่งั้นทำไมคุณถึงต้องรำคาญ)
MathematicalOrchid

ฉันไม่แน่ใจว่า Haskell สนับสนุนสิ่งนี้หรือไม่ แต่คุณสามารถกำหนด monad ได้ทั้งในแง่ของ >> = และส่งคืนหรือเข้าร่วมและ ap >> = และการกลับมาเป็นสิ่งที่ทำให้ monads นั้นมีประโยชน์ในทางปฏิบัติ แต่การเข้าร่วมและ ap ช่วยให้เข้าใจได้ง่ายขึ้นว่า monad คืออะไร
Jeremy List

15
มาจากพื้นหลังการเขียนโปรแกรมที่ไม่ใช่คณิตศาสตร์ไม่ใช่ฟังก์ชั่นคำตอบนี้เหมาะสมที่สุดสำหรับฉัน
jrahhali

10
นี่เป็นคำตอบแรกที่ทำให้ฉันมีความคิดว่านรกคืออะไร ขอบคุณที่หาวิธีอธิบาย!
หุ่นยนต์อาจ

186

ที่จริงตรงกันข้ามกับความเข้าใจทั่วไปของ 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 เป็นหนึ่งในสิ่งเหล่านี้ที่การทำความเข้าใจกับสมองของคุณโดยการฝึกฝนวันหนึ่งคุณก็รู้ตัวว่าคุณเข้าใจพวกเขา


-> เป็นแอพพลิเคชั่นฟังก์ชั่นการเชื่อมโยงทางขวา, การทำมิเรอร์ซึ่งสัมพันธ์กันทางซ้ายดังนั้นการออกจากวงเล็บไม่ได้สร้างความแตกต่างที่นี่
Matthias Benkard

1
ฉันไม่คิดว่านี่เป็นคำอธิบายที่ดีมากเลย Monads เป็นเพียงวิธี? โอเควิธีไหน เหตุใดฉันจึงไม่แค็ปซูลโดยใช้คลาสแทน monad
Breton

4
@ mb21: ในกรณีที่คุณเพิ่งชี้ให้เห็นว่ามีวงเล็บจำนวนมากเกินไปโปรดทราบว่า a-> b-> c นั้นสั้นเพียง a -> (b-> c) การเขียนตัวอย่างเฉพาะนี้เป็น (a -> b) -> (Ta -> Tb) กำลังพูดอย่างเคร่งครัดเพียงแค่เพิ่มตัวละครที่ไม่จำเป็น แต่มันเป็นคุณธรรม "สิ่งที่ถูกต้องที่ต้องทำ" ในเชิงศีลธรรมเพราะมันเน้นว่า fmap แผนที่ฟังก์ชันของ b เป็นฟังก์ชันประเภท Ta -> Tb และในขั้นต้นนั้นเป็นสิ่งที่ผู้ทำหน้าที่ในทฤษฎีหมวดหมู่และนั่นคือสิ่งที่พระมา
Nikolaj-K

1
คำตอบนี้ทำให้เข้าใจผิด พระบางแห่งไม่มี "ผู้ห่อหุ้ม" เลยฟังก์ชั่นดังกล่าวจากค่าคงที่

1
@DanMandel Monads เป็นรูปแบบการออกแบบที่ใส่เสื้อคลุมชนิดข้อมูลของตัวเอง Monads ได้รับการออกแบบในทางที่เป็นนามธรรมรหัสสำเร็จรูป ดังนั้นเมื่อคุณเรียก Monad ในรหัสของคุณมันจะทำสิ่งต่าง ๆ ที่คุณไม่ต้องกังวล คิดเกี่ยวกับ Nullable <T> หรือ IEnumerable <T> พวกเขาทำอะไรอยู่เบื้องหลัง Thats Monad
sksallaj

168

แต่คุณสามารถคิดค้น Monads ได้!

sigfpe พูดว่า:

แต่สิ่งเหล่านี้นำเสนอพระสงฆ์เป็นสิ่งลึกลับที่ต้องการคำอธิบาย แต่สิ่งที่ฉันต้องการโต้แย้งก็คือพวกเขาไม่ได้เป็นความลับเลย ในความเป็นจริงต้องเผชิญกับปัญหาต่าง ๆ ในการเขียนโปรแกรมการทำงานที่คุณจะได้รับนำไปสู่การแก้ปัญหาบางอย่างซึ่งทั้งหมดนี้เป็นตัวอย่างของพระ อันที่จริงฉันหวังว่าจะให้คุณประดิษฐ์มันขึ้นมาในตอนนี้ถ้าคุณยังไม่ได้ทำ จากนั้นเป็นขั้นตอนเล็ก ๆ ที่จะสังเกตเห็นว่าโซลูชั่นเหล่านี้ทั้งหมดเป็นวิธีการแก้ปัญหาเดียวกันในการปลอม และหลังจากอ่านสิ่งนี้แล้วคุณอาจอยู่ในตำแหน่งที่ดีขึ้นในการทำความเข้าใจกับเอกสารอื่น ๆ ในพระเพราะคุณจำทุกสิ่งที่คุณเห็นว่าเป็นสิ่งที่คุณประดิษฐ์ขึ้นมาแล้ว

ปัญหาหลายอย่างที่ monads พยายามแก้ไขเกี่ยวข้องกับปัญหาของผลข้างเคียง ดังนั้นเราจะเริ่มด้วย (โปรดทราบว่า monads อนุญาตให้คุณทำมากกว่าจัดการกับผลข้างเคียงโดยเฉพาะอย่างยิ่งวัตถุคอนเทนเนอร์หลายประเภทสามารถดูได้เป็น monads การแนะนำบางอย่างให้ monads พบว่าเป็นการยากที่จะกระทบยอดการใช้ monads ที่แตกต่างกันทั้งสองนี้ อื่น ๆ.)

ในภาษาการเขียนโปรแกรมที่จำเป็นเช่น C ++ ฟังก์ชั่นจะทำงานไม่เหมือนกับฟังก์ชันของคณิตศาสตร์ ตัวอย่างเช่นสมมติว่าเรามีฟังก์ชั่น C ++ ที่รับอาร์กิวเมนต์ floating point เพียงตัวเดียวและส่งกลับผลลัพธ์ของ floating point เผินๆมันอาจฟังดูคล้ายฟังก์ชั่นการคำนวณทางคณิตศาสตร์เพื่อ reals แต่ฟังก์ชั่น C ++ สามารถทำได้มากกว่าเพียงแค่ส่งกลับตัวเลขที่ขึ้นอยู่กับข้อโต้แย้งของมัน มันสามารถอ่านและเขียนค่าของตัวแปรทั่วโลกเช่นเดียวกับการเขียนผลลัพธ์ไปยังหน้าจอและรับอินพุตจากผู้ใช้ อย่างไรก็ตามในภาษาที่ใช้งานได้จริงฟังก์ชั่นสามารถอ่านได้เฉพาะสิ่งที่มีให้ในการโต้แย้งและวิธีเดียวที่มันจะมีผลต่อโลกคือผ่านค่าที่ส่งคืน


9
... วิธีที่ดีที่สุดไม่เพียง แต่บนอินเทอร์เน็ต แต่ทุกที่ (เอกสารต้นฉบับของ Wadler Monads สำหรับการเขียนโปรแกรมการทำงานที่ฉันกล่าวถึงในคำตอบของฉันด้านล่างก็ดีเช่นกัน) ไม่มี zillions ของบทเรียนต่อการเปรียบเทียบมาใกล้
ShreevatsaR

13
การแปล JavaScript ของโพสต์ของ Sigfpeนี้เป็นวิธีที่ดีที่สุดในการเรียนรู้ monads สำหรับผู้ที่ยังไม่ก้าวร้าว Haskell ขั้นสูง!
Sam Watkins

1
นี่คือวิธีที่ฉันเรียนรู้ว่า monad คืออะไร การที่ผู้อ่านเดินผ่านกระบวนการประดิษฐ์แนวคิดมักเป็นวิธีที่ดีที่สุดในการสอนแนวคิด
Jordan

อย่างไรก็ตามฟังก์ชั่นที่รับวัตถุหน้าจอเป็นอาร์กิวเมนต์และส่งคืนสำเนาที่มีการดัดแปลงข้อความจะบริสุทธิ์
Dmitri Zaitsev

87

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 นั้นเกี่ยวกับอะไรและทำไมจึงมีประโยชน์


คุณหมายถึงอะไรโดย "แมปฟังก์ชันกับ"?
Casebash

Casebash ฉันตั้งใจอย่างไม่เป็นทางการในการแนะนำ ดูตัวอย่างใกล้ถึงจุดสิ้นสุดเพื่อให้เข้าใจว่า "การทำแผนที่ฟังก์ชั่น" มีความหมายว่าอย่างไร
Chris Conway

3
Monad ไม่ใช่ประเภทข้อมูล มันเป็นกฎของฟังก์ชั่นการเขียน: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

@DmitriZaitsev ถูกต้อง Monads ให้ข้อมูลชนิดของตัวเองจริง ๆ Monads arent data type
sksallaj

78

ก่อนอื่นคุณควรเข้าใจว่านักแสดงคืออะไร ก่อนหน้านั้นเข้าใจฟังก์ชั่นการสั่งซื้อที่สูงขึ้น

ฟังก์ชั่นขั้นสูงเป็นเพียงฟังก์ชั่นที่ใช้ฟังก์ชั่นเป็นอาร์กิวเมนต์

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จะต้องเป็นตัวตนสำหรับดังกล่าวว่าjoinjoin (pure x) == x


3
นอกจากนี้เล็กน้อยเพื่อ def ของฟังก์ชั่นการสั่งซื้อที่สูงขึ้น: พวกเขาสามารถใช้ฟังก์ชั่นหรือผลตอบแทน นั่นเป็นเหตุผลที่พวกเขา 'สูง' 'เพราะพวกเขาทำสิ่งต่าง ๆ ด้วยตัวเอง
Kevin Won

9
ตามคำจำกัดความนั้นการเติมเป็นฟังก์ชันลำดับที่สูงกว่า มันต้องใช้ตัวเลขและส่งกลับฟังก์ชันที่เพิ่มหมายเลขนั้นไปยังอีกหมายเลขหนึ่ง ดังนั้นไม่ฟังก์ชั่นการสั่งซื้อที่สูงขึ้นเป็นฟังก์ชั่นที่เคร่งครัดซึ่งโดเมนประกอบด้วยฟังก์ชั่น
Apocalisp

วิดีโอ ' Brian Beckman: ไม่ต้องกลัว Monad ' ตามตรรกะบรรทัดเดียวกันนี้
icc97

48

[ข้อจำกัดความรับผิดชอบ: ฉันยังคงพยายามที่จะบวชอย่างเต็มที่ ต่อไปนี้เป็นเพียงสิ่งที่ฉันเข้าใจ ถ้ามันผิดหวังว่าคนที่มีความรู้จะโทรหาฉันบนพรม]

Arnar เขียนว่า:

Monads เป็นเพียงวิธีหนึ่งในการห่อสิ่งต่าง ๆ และจัดเตรียมวิธีดำเนินการกับสิ่งที่ถูกห่อโดยไม่ต้องเปิดมัน

นั่นแหละ แนวคิดจะเป็นดังนี้:

  1. คุณรับค่าบางอย่างและห่อด้วยข้อมูลเพิ่มเติม เช่นเดียวกับค่าที่เป็นชนิดบางอย่าง (เช่นจำนวนเต็มหรือสตริง) ดังนั้นข้อมูลเพิ่มเติมจึงเป็นชนิดที่แน่นอน

    เช่นว่าข้อมูลเพิ่มเติมอาจจะมีหรือMaybeIO

  2. จากนั้นคุณมีผู้ให้บริการบางรายที่อนุญาตให้คุณดำเนินการกับข้อมูลที่ถูกห่อในขณะที่ดำเนินการพร้อมกับข้อมูลเพิ่มเติมนั้น ตัวดำเนินการเหล่านี้ใช้ข้อมูลเพิ่มเติมเพื่อตัดสินใจว่าจะเปลี่ยนลักษณะการทำงานของการดำเนินการกับค่าที่ถูกห่อ

    เช่นที่Maybe Intอาจจะเป็นหรือJust Int Nothingตอนนี้ถ้าคุณเพิ่มMaybe IntไปยังMaybe Intผู้ประกอบการจะตรวจสอบเพื่อดูว่าพวกเขามีทั้งที่Just Intอยู่ภายในและถ้าเป็นเช่นนั้นจะแกะInts, ผ่านพวกเขาดำเนินการนอกจากนี้อีกห่อส่งผลให้Intเข้าใหม่Just Int(ซึ่งเป็นที่ถูกต้องMaybe Int) Maybe Intและทำให้กลับมาเป็น แต่ถ้าหนึ่งของพวกเขาเป็นNothingภายในดำเนินการนี้ก็จะกลับทันทีอีกครั้งซึ่งเป็นที่ถูกต้องNothing Maybe Intด้วยวิธีนี้คุณสามารถทำเป็นว่าคุณMaybe Intเป็นเพียงตัวเลขปกติและทำการคำนวณทางคณิตศาสตร์กับพวกมัน หากคุณกำลังจะได้รับNothingสมการของคุณจะยังคงผลิตผลขวา - โดยคุณไม่ต้องตรวจสอบครอกสำหรับNothingทุก

Maybeแต่ตัวอย่างเป็นเพียงสิ่งที่เกิดขึ้น หากข้อมูลเพิ่มเติมเป็นแบบIOนั้นตัวดำเนินการพิเศษที่กำหนดไว้สำหรับIOs จะถูกเรียกแทนและมันสามารถทำบางสิ่งที่แตกต่างออกไปโดยสิ้นเชิงก่อนที่จะทำการเพิ่ม (ตกลงการรวมสองIO Ints เข้าด้วยกันอาจเป็นเรื่องไร้สาระ - ฉันยังไม่แน่ใจ) (นอกจากนี้หากคุณให้ความสนใจกับMaybeตัวอย่างคุณสังเกตว่า "การตัดค่าด้วยสิ่งพิเศษ" นั้นไม่ถูกต้องเสมอไป แต่มันยาก เป็นที่แน่นอนถูกต้องและแม่นยำโดยไม่ต้องมีการพิสูจน์ไม่ได้)

โดยพื้นฐานแล้ว“ monad” หมายถึง“ รูปแบบ”อย่างคร่าว ๆ แต่แทนที่จะหนังสือที่เต็มไปด้วยรูปแบบการอธิบายอย่างไม่เป็นทางการและตั้งชื่อโดยเฉพาะขณะนี้คุณมีภาษาสร้าง - ไวยากรณ์และทั้งหมด - ที่ช่วยให้คุณประกาศรูปแบบใหม่เป็นสิ่งที่อยู่ในโปรแกรมของคุณ (ความไม่แน่ชัดที่นี่คือรูปแบบทั้งหมดจะต้องเป็นไปตามรูปแบบเฉพาะดังนั้น monad จึงไม่ธรรมดาเหมือนรูปแบบ แต่ฉันคิดว่านั่นเป็นคำที่ใกล้เคียงที่สุดที่คนส่วนใหญ่รู้จักและเข้าใจ)

และนั่นคือสาเหตุที่ผู้คนพบว่าพระสงฆ์สับสนมากเพราะเป็นแนวคิดทั่วไป การถามสิ่งที่ทำให้บางสิ่งบางอย่างเป็นแบบ monad คลุมเครือเหมือนกับการถามว่าอะไรที่ทำให้รูปแบบเป็นอะไร

แต่คิดว่าผลกระทบของการมีการสนับสนุนทางไวยากรณ์ในภาษาสำหรับแนวคิดของรูปแบบ: แทนที่จะต้องอ่านหนังสือGang of Fourและจดจำการสร้างรูปแบบเฉพาะคุณเพียงแค่เขียนรหัสที่ใช้รูปแบบนี้ในผู้ไม่เชื่อเรื่องพระเจ้า วิธีทั่วไปครั้งเดียวแล้วคุณจะทำ! จากนั้นคุณสามารถนำรูปแบบนี้กลับมาใช้ใหม่เช่นผู้เยี่ยมชมหรือกลยุทธ์หรือส่วนหน้าหรืออะไรก็ตามเพียงแค่ตกแต่งการดำเนินการในโค้ดของคุณด้วยโดยไม่ต้องนำมันไปใช้ซ้ำแล้วซ้ำอีก!

นั่นคือสาเหตุที่คนที่เข้าใจพระพบว่าพวกเขามีประโยชน์มาก : มันไม่ใช่แนวคิดหอคอยงาช้างที่ snobs ทางปัญญาภูมิใจในการทำความเข้าใจ (ตกลงแน่นอนเกินไป teehee) แต่จริงๆแล้วทำให้โค้ดง่ายขึ้น


12
บางครั้งคำอธิบายจาก "ผู้เรียน" (เช่นคุณ) มีความเกี่ยวข้องกับผู้เรียนรายอื่นมากกว่าคำอธิบายที่มาจากผู้เชี่ยวชาญ ผู้เรียนคิดเหมือนกัน :)
Adrian

สิ่งที่ทำให้ monad M (M a) -> M aบางสิ่งบางอย่างคือการดำรงอยู่ของฟังก์ชั่นที่มีชนิด ความจริงที่ว่าคุณสามารถเปลี่ยนให้เป็นประเภทหนึ่งM a -> (a -> M b) -> M bเป็นสิ่งที่ทำให้พวกเขามีประโยชน์
รายการเจเรมี

"monad" แปลว่า "รูปแบบ" โดยประมาณ ... ไม่
ขอบคุณ

44

หลังจากพยายามมากฉันคิดว่าในที่สุดฉันก็เข้าใจ monad หลังจากอ่านคำวิจารณ์ที่ยาวเหยียดของตัวเองเพื่อหาคำตอบที่ได้รับการโหวตมากที่สุดฉันจะให้คำอธิบายนี้

มีคำถามสามข้อที่ต้องตอบให้เข้าใจพระ:

  1. ทำไมคุณต้องใช้ Monad
  2. Monad คืออะไร
  3. 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 Monad เป็นเพียงนักแสดงที่มีผลผูกพันและกลับมา ผูกและกลับมาให้ลำดับ แต่พวกเขาก็ให้สิ่งอื่นเช่นกัน นอกจากนี้โปรดทราบว่าภาษาคำสั่งที่คุณชื่นชอบคือ IO monad แฟนซีที่มีคลาส OO ทำให้ง่ายต่อการกำหนด monads หมายความว่ามันง่ายต่อการใช้รูปแบบการแปล - กำหนด dsl เป็น monad และตีความมัน!
nomen

นี่คือการดำเนินการ: github.com/brianspinos777/Programming_cheat_sheets/blob/master/…
Brian Joseph Spinos

38

หลังจากให้คำตอบสำหรับคำถามนี้เมื่อไม่กี่ปีที่ผ่านมาฉันเชื่อว่าฉันสามารถปรับปรุงและทำให้การตอบกลับง่ายขึ้นด้วย ...

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 คือสามสิ่ง:

  1. Mเปลือกสำหรับการถือครอง Monad ข้อมูลที่เกี่ยวข้อง
  2. bindฟังก์ชั่นการใช้งานที่จะทำให้การใช้ข้อมูลเปลือกในการประยุกต์ใช้ฟังก์ชั่นประกอบกับค่าเนื้อหา (s) ที่พบภายในเปลือกและ
  3. ฟังก์ชั่นการจัดองค์ประกอบของแบบฟอร์มa -> Mbสร้างผลลัพธ์ที่มีข้อมูลการจัดการแบบ monadic

โดยทั่วไปแล้วอินพุตไปยังฟังก์ชันมีข้อ จำกัด มากกว่าเอาต์พุตซึ่งอาจรวมถึงสิ่งต่าง ๆ เช่นเงื่อนไขข้อผิดพลาด ดังนั้นMbโครงสร้างผลลัพธ์จึงมีประโยชน์มาก 0ตัวอย่างเช่นผู้ประกอบการในส่วนที่ไม่ได้กลับจำนวนเมื่อหารเป็น

นอกจากนี้monads อาจรวมถึงฟังก์ชั่นการตัดที่ห่อค่า, 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จะต้องพิจารณาก่อนที่ความคุ้มค่าจะกลายเป็นนำไปใช้และผลที่ในการเปิดนำไปใช้กับfg


"... แต่ฉันหวังว่าคนอื่นจะเห็นว่ามีประโยชน์"แน่นอนว่ามันมีประโยชน์สำหรับฉันแม้จะมีประโยคที่เน้นไว้ทั้งหมด: D

นี่เป็นคำอธิบายที่กระชับและชัดเจนที่สุดของพระที่ฉันเคยอ่าน / ดู / ได้ยิน ขอบคุณ!
James

มีความแตกต่างที่สำคัญระหว่าง Monad และ Monoid Monad เป็นกฎในการ "เขียน" ฟังก์ชั่นระหว่างประเภทที่แตกต่างกันดังนั้นพวกเขาจึงไม่ได้ดำเนินการแบบไบนารีตามที่จำเป็นสำหรับ Monoids ดูที่นี่สำหรับรายละเอียดเพิ่มเติม: stackoverflow.com/questions/2704652/…
Dmitri Zaitsev

ใช่. คุณถูก. บทความของคุณอยู่เหนือหัวฉัน :) อย่างไรก็ตามฉันพบว่าการรักษานี้มีประโยชน์มาก (และเพิ่มให้กับฉันเพื่อเป็นแนวทางแก่ผู้อื่น) ขอบคุณที่หัวของคุณขึ้นมา: stackoverflow.com/a/7829607/1612190
George

2
คุณอาจสับสนทฤษฎีกลุ่มพีชคณิตกับหมวดหมู่ทฤษฎีที่ Monad มาจาก อดีตคือทฤษฎีของกลุ่มพีชคณิตซึ่งไม่เกี่ยวข้องกัน
Dmitri Zaitsev

37

Monad คือรูปแบบของ "ตัวดำเนินการพิมพ์" อย่างมีประสิทธิภาพ มันจะทำสามสิ่ง ก่อนอื่นมันจะ "ตัด" (หรือแปลงเป็นอย่างอื่น) ค่าประเภทหนึ่งเป็นประเภทอื่น (โดยทั่วไปเรียกว่า "ประเภท monadic") ประการที่สองมันจะทำให้การดำเนินงานทั้งหมด (หรือฟังก์ชั่น) สามารถใช้ได้กับประเภทพื้นฐานที่มีอยู่ในประเภท monadic ในที่สุดมันก็จะให้การสนับสนุนสำหรับการรวมตัวของมันเองกับ Monad อื่นเพื่อผลิตคอมโพสิต Monad

"บางที monad" เป็นหลักเทียบเท่า "ประเภท nullable" ใน Visual Basic / C # ใช้ชนิด "T" ที่ไม่มีค่า null และแปลงเป็น "Nullable <T>" จากนั้นกำหนดว่าตัวดำเนินการไบนารีทั้งหมดหมายถึงอะไรใน Nullable <T>

ผลข้างเคียงจะถูกแสดงพร้อม ๆ กัน มีการสร้างโครงสร้างที่เก็บคำอธิบายของผลข้างเคียงพร้อมกับค่าส่งคืนของฟังก์ชัน การดำเนินการ "ยก" แล้วคัดลอกผลข้างเคียงเป็นค่าที่ถูกส่งผ่านระหว่างฟังก์ชั่น

พวกเขาถูกเรียกว่า "monads" แทนที่จะเป็นชื่อที่เข้าใจง่ายกว่าของ "ตัวดำเนินการพิมพ์" ด้วยเหตุผลหลายประการ:

  1. Monads มีข้อ จำกัด ในสิ่งที่พวกเขาสามารถทำได้ (ดูรายละเอียดที่ชัดเจน)
  2. ข้อ จำกัด เหล่านั้นรวมถึงข้อเท็จจริงที่ว่ามีการดำเนินงานสามอย่างที่เกี่ยวข้องสอดคล้องกับโครงสร้างของสิ่งที่เรียกว่า monad ในหมวดหมู่ทฤษฎีซึ่งเป็นสาขาที่ไม่ชัดเจนของคณิตศาสตร์
  3. พวกเขาได้รับการออกแบบโดยผู้สนับสนุนของภาษาฟังก์ชั่น "บริสุทธิ์"
  4. ผู้เสนอภาษาที่ใช้งานได้อย่างบริสุทธิ์เช่นสาขาคณิตศาสตร์ที่คลุมเครือ
  5. เนื่องจากคณิตศาสตร์นั้นไม่ชัดเจนและ monads นั้นเกี่ยวข้องกับรูปแบบการเขียนโปรแกรมเฉพาะผู้คนมักจะใช้คำว่า monad เป็นการจับมือลับ ด้วยเหตุนี้จึงไม่มีใครใส่ใจที่จะลงทุนในชื่อที่ดีขึ้น

1
Monads ไม่ใช่ 'ออกแบบ' พวกมันถูกนำมาใช้จากโดเมนหนึ่ง (ทฤษฎีหมวดหมู่) ไปยังอีกโดเมนหนึ่ง (I / O ในภาษาการเขียนโปรแกรมที่ใช้งานได้จริง) นิวตัน 'ออกแบบ' แคลคูลัสหรือไม่
Jared Updike

1
จุดที่ 1 และ 2 ด้านบนถูกต้องและมีประโยชน์ คะแนน 4 และ 5 เป็นโฆษณาประเภทหนึ่งแม้ว่าจะมากหรือน้อยก็ตาม พวกเขาไม่ได้ช่วยอธิบายพระ
Jared Updike

13
Re: 4, 5: "Secret handshake" เป็นปลาเฮอริ่งแดง การเขียนโปรแกรมเต็มไปด้วยศัพท์แสง Haskell เพิ่งจะเรียกสิ่งที่มันเป็นโดยไม่ต้องแสร้งทำเป็นค้นพบบางสิ่งบางอย่าง ถ้ามันมีอยู่ในคณิตศาสตร์แล้วทำไมต้องตั้งชื่อใหม่ให้มัน? ชื่อไม่ใช่เหตุผลที่ผู้คนไม่ได้รับพระ พวกเขาเป็นแนวคิดที่ลึกซึ้ง คนทั่วไปอาจเข้าใจการบวกและการคูณทำไมพวกเขาไม่เข้าใจแนวคิดของกลุ่ม Abelian เพราะมันเป็นนามธรรมและทั่วไปมากขึ้นและคน ๆ นั้นไม่ได้ทำงานเพื่อคลุมหัวแนวคิด การเปลี่ยนชื่อจะไม่ช่วย
Jared Updike

16
เฮ้อ ... ฉันไม่ได้โจมตีแฮสเค็ลล์ ... ดังนั้นฉันไม่ได้รับบิตเกี่ยวกับการเป็น "ad hominem" ใช่แคลคูลัสคือ "ออกแบบ" นั่นเป็นเหตุผลที่ตัวอย่างเช่นนักเรียนแคลคูลัสได้รับการสอนสัญกรณ์ Leibniz มากกว่าสิ่งที่นิกตันใช้ การออกแบบที่ดีขึ้น ชื่อที่ดีช่วยให้เข้าใจมาก ถ้าฉันเรียกกลุ่ม Abelian ว่า "รอยเหี่ยวย่นที่เหินห่าง" คุณอาจมีปัญหาในการเข้าใจฉัน คุณอาจจะพูดว่า "แต่ชื่อนั้นไร้สาระ" ไม่มีใครเคยเรียกพวกเขาว่า สำหรับคนที่ไม่เคยได้ยินทฤษฎีหมวดหมู่ "monad" ฟังดูไร้สาระ
Scott Wisniewski

4
@Scott: ขออภัยถ้าความคิดเห็นที่กว้างขวางของฉันทำให้ดูเหมือนว่าฉันได้รับการป้องกันเกี่ยวกับ Haskell ฉันสนุกกับอารมณ์ขันของคุณเกี่ยวกับการจับมือลับและคุณจะทราบว่าฉันบอกว่ามันเป็นเรื่องจริงมากหรือน้อย :-) ถ้าคุณเรียกกลุ่ม Abelian ว่า "ริ้วรอยเหี่ยวย่น distended" คุณจะทำผิดพลาดเหมือนกันกับการพยายามให้ monads "ชื่อที่ดีกว่า" (เทียบกับ F # "การคำนวณการแสดงออก"): คำนี้มีอยู่และคนที่สนใจรู้ว่า monads มี แต่ไม่ใช่สิ่งที่ "สิ่งที่อบอุ่นเลือน" คือ (หรือ "การคำนวณการแสดงออก") ถ้าฉันเข้าใจการใช้คำว่า "ตัวดำเนินการพิมพ์" อย่างถูกต้องมีตัวดำเนินการชนิดอื่น ๆ มากมายกว่าพระ
Jared Updike

35

(ดูคำตอบได้ที่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 สำหรับการเขียนโปรแกรมการทำงาน จริง ๆ แล้วมันมีตัวอย่างที่เป็นประโยชน์และไม่เป็นแรงจูงใจซึ่งแตกต่างจากบทช่วยสอนที่ประดิษฐ์ขึ้นมากมาย


2
ปัญหาเดียวของกระดาษของ Wadler คือสัญกรณ์ต่างกัน แต่ฉันยอมรับว่ากระดาษนั้นน่าสนใจและมีแรงจูงใจที่ชัดเจนในการใช้พระ
Jared Updike

+1 สำหรับ "การสอนผิดแบบ monad" แบบฝึกหัดเกี่ยวกับ monads นั้นคล้ายกับการมีบทเรียนหลายบทที่พยายามอธิบายแนวคิดของตัวเลขจำนวนเต็ม บทแนะนำหนึ่งจะบอกว่า "1 คล้ายกับแอปเปิ้ล"; บทช่วยสอนอื่นบอกว่า "2 เหมือนลูกแพร์"; คนที่สามพูดว่า "3 เป็นสีส้ม" แต่คุณไม่เคยได้รับภาพรวมทั้งหมดจากการกวดวิชาใด ๆ สิ่งที่ฉันนำมาจากที่นั่นคือ monads เป็นแนวคิดนามธรรมซึ่งสามารถใช้เพื่อวัตถุประสงค์ที่แตกต่างกันมาก
stakx - ไม่สนับสนุนอีก

@stakx: ใช่จริง แต่ฉันไม่ได้หมายความว่าพระเป็นสิ่งที่คุณไม่สามารถเรียนรู้หรือไม่ควรเรียนรู้ เฉพาะที่ดีที่สุดที่จะเรียนรู้มันหลังจากที่คุณได้เห็นตัวอย่างที่เป็นรูปธรรมเพียงพอที่จะรับรู้สิ่งที่เป็นนามธรรมพื้นฐาน ดูคำตอบอื่น ๆ ของฉันที่นี่
ShreevatsaR

5
บางครั้งฉันรู้สึกว่ามีบทเรียนมากมายที่พยายามโน้มน้าวผู้อ่านว่า monads มีประโยชน์โดยใช้โค้ดที่ทำสิ่งที่ซับซ้อนหรือมีประโยชน์ ที่ขัดขวางความเข้าใจของฉันเป็นเวลาหลายเดือน ฉันไม่ได้เรียนรู้วิธีการนั้น ฉันชอบที่จะเห็นรหัสที่ง่ายมากทำสิ่งที่โง่ที่ฉันสามารถผ่านจิตใจและฉันไม่สามารถหาตัวอย่างเช่นนี้ ฉันไม่สามารถเรียนรู้ได้ถ้าตัวอย่างแรกเป็น monad เพื่อแยกไวยากรณ์ที่ซับซ้อน ฉันสามารถเรียนรู้ว่ามันเป็น monad ที่จะรวมจำนวนเต็มหรือไม่
Rafael S. Calsaverini

การอ้างถึงคอนสตรัคเตอร์ประเภทเดียวเท่านั้นไม่สมบูรณ์: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

23

Monads คือการควบคุมโฟลว์ของชนิดข้อมูลนามธรรมให้กับข้อมูล

กล่าวอีกนัยหนึ่งนักพัฒนาหลายคนคุ้นเคยกับแนวคิดชุดรายการพจนานุกรม (หรือแฮชหรือแผนที่) และต้นไม้ ภายในชนิดข้อมูลเหล่านั้นมีกรณีพิเศษมากมาย (เช่น InsertionOrderPreservingIdentityHashMap)

อย่างไรก็ตามเมื่อเผชิญหน้ากับโปรแกรม "โฟลว์" นักพัฒนาหลายคนยังไม่ได้สัมผัสกับสิ่งก่อสร้างอื่น ๆ อีกมากมายกว่าถ้าเปลี่ยน / เคสทำในขณะที่ goto (grr) และ (อาจ) การปิด

ดังนั้น monad จึงเป็นโครงสร้างการควบคุมการไหล วลีที่ดีกว่าสำหรับแทนที่ monad คือ 'ประเภทการควบคุม'

ดังนั้น monad จึงมีช่องสำหรับควบคุมตรรกะหรือคำสั่งหรือฟังก์ชั่นซึ่งเทียบเท่ากับโครงสร้างข้อมูลที่จะกล่าวว่าโครงสร้างข้อมูลบางอย่างช่วยให้คุณสามารถเพิ่มข้อมูลและลบออกได้

ตัวอย่างเช่น "ถ้า" monad:

if( clause ) then block

ที่ง่ายที่สุดมีสองช่องคือส่วนคำสั่งและบล็อก โดยifปกติ Monad จะถูกสร้างขึ้นเพื่อประเมินผลลัพธ์ของประโยคและหากไม่ใช่เท็จให้ประเมินบล็อก นักพัฒนาหลายคนไม่ได้รับการแนะนำให้รู้จักกับพระเมื่อพวกเขาเรียนรู้ 'ถ้า' และไม่จำเป็นต้องเข้าใจพระในการเขียนตรรกะที่มีประสิทธิภาพ

Monads อาจมีความซับซ้อนมากขึ้นเช่นเดียวกับที่โครงสร้างข้อมูลอาจมีความซับซ้อนมากขึ้น แต่มี monad หลายประเภทที่อาจมีความหมายคล้ายกัน แต่มีการใช้งานและไวยากรณ์ที่แตกต่างกัน

แน่นอนในลักษณะเดียวกับที่โครงสร้างข้อมูลอาจถูกทำซ้ำหรือข้ามไปอาจมีการประเมิน monads

คอมไพเลอร์อาจมีหรือไม่มีการสนับสนุนสำหรับพระที่ผู้ใช้กำหนด Haskell ไม่แน่นอน Ioke มีความสามารถคล้ายกันถึงแม้ว่าคำว่า monad ไม่ได้ใช้ในภาษา


14

บทช่วยสอน 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 คือ >> = (ออกเสียงว่า "ผูก") แต่มีน้ำตาลประโยค ("ทำ") ที่ให้คุณใช้วงเล็บปีกกาและเครื่องหมายอัฒภาคและ / หรือการเยื้องและการขึ้นบรรทัดใหม่


9

ฉันคิดถึง Monads ในวิธีที่ต่างออกไปเมื่อเร็ว ๆ นี้ ฉันคิดว่าพวกเขาจะสรุปลำดับการดำเนินการในรูปแบบทางคณิตศาสตร์ซึ่งทำให้เกิดความหลากหลายในรูปแบบใหม่

หากคุณกำลังใช้ภาษาที่จำเป็นและคุณเขียนนิพจน์ตามลำดับรหัส ALWAYS จะทำงานตามลำดับนั้นอย่างแน่นอน

และในกรณีง่าย ๆ เมื่อคุณใช้ monad รู้สึกเหมือนกัน - คุณกำหนดรายการของนิพจน์ที่เกิดขึ้นตามลำดับ ยกเว้นว่าขึ้นอยู่กับว่าคุณใช้ monad ใดรหัสของคุณอาจทำงานตามลำดับ (เช่นใน IO monad) ขนานกับหลาย ๆ รายการพร้อมกัน (เช่นใน List monad) มันอาจหยุดบางส่วน (เช่นในบางที monad) อาจหยุดชั่วคราวเพื่อดำเนินการต่อในภายหลัง (เช่นใน Resumption monad) อาจย้อนกลับและเริ่มต้นจากจุดเริ่มต้น (เช่นในธุรกรรม Monad) หรืออาจย้อนกลับบางส่วนเพื่อลองตัวเลือกอื่น ๆ (เช่นใน Logic monad) .

และเนื่องจาก monads นั้นเป็น polymorphic คุณจึงสามารถเรียกใช้รหัสเดียวกันใน monads ต่าง ๆ ได้ขึ้นอยู่กับความต้องการของคุณ

นอกจากนี้ในบางกรณีมันเป็นไปได้ที่จะรวม monads เข้าด้วยกัน (กับ monad transformers) เพื่อรับคุณสมบัติหลายอย่างในเวลาเดียวกัน


9

ฉันยังใหม่กับ monads แต่ฉันคิดว่าฉันจะแชร์ลิงก์ที่ฉันพบว่ารู้สึกดีมากที่จะอ่าน (พร้อมรูปภาพ !!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ monads-for-the-layman / (ไม่มีส่วนเกี่ยวข้อง)

โดยพื้นฐานแล้วแนวคิดที่อบอุ่นและคลุมเครือที่ฉันได้รับจากบทความคือแนวคิดที่ว่า monads นั้นเป็นอะแดปเตอร์ที่อนุญาตให้ฟังก์ชั่นที่แตกต่างกันในการทำงานแบบเรียงความคือสามารถสตริงฟังก์ชั่นที่หลากหลายและผสมกันได้ ประเภทและเช่น ดังนั้นฟังก์ชั่น BIND จะทำหน้าที่เก็บแอปเปิ้ลกับแอปเปิ้ลและส้มด้วยส้มเมื่อเราพยายามสร้างอะแดปเตอร์เหล่านี้ และฟังก์ชั่น LIFT รับผิดชอบการใช้งานฟังก์ชั่น "ระดับล่าง" และ "อัพเกรด" เพื่อใช้งานกับฟังก์ชั่น BIND และสามารถจัดองค์ประกอบได้เช่นกัน

ฉันหวังว่าฉันเข้าใจถูกต้องและที่สำคัญกว่านั้นหวังว่าบทความจะมีมุมมองที่ถูกต้องเกี่ยวกับพระ หากไม่มีอะไรอื่นบทความนี้ช่วยกระตุ้นความอยากอาหารของฉันให้เรียนรู้มากขึ้นเกี่ยวกับพระ


ตัวอย่างของไพ ธ อนทำให้เข้าใจง่าย! ขอบคุณสำหรับการแบ่งปัน.
Ryan Efendy

8

นอกจากคำตอบที่ดีเลิศข้างต้นฉันขอเสนอลิงก์ไปยังบทความต่อไปนี้ (โดย Patrick Thomson) ซึ่งอธิบาย monads โดยเกี่ยวข้องกับแนวคิดเกี่ยวกับไลบรารี JavaScript jQuery (และวิธีการใช้ "วิธีการผูกมัด" เพื่อจัดการ DOM) : jQuery เป็น Monad

เอกสาร jQueryตัวเองไม่ได้หมายถึงคำว่า "monad" แต่การเจรจาเกี่ยวกับการ "สร้างรูปแบบ" ซึ่งน่าจะเป็นที่คุ้นเคยมากขึ้น นี่ไม่ได้เปลี่ยนความจริงที่ว่าคุณมีพระที่เหมาะสมอยู่ที่นั่นบางทีอาจจะไม่ได้รู้ตัว


หากคุณใช้ jQuery คำอธิบายนี้มีประโยชน์มากโดยเฉพาะถ้า Haskell ของคุณไม่แข็งแรง
byteclub

10
JQuery ไม่ได้เป็น monad อย่างชัดเจน บทความที่เชื่อมโยงผิด
โทนี่มอร์ริส

1
การเป็น "สำคัญ" นั้นไม่น่าเชื่อมาก สำหรับการอภิปรายที่มีประโยชน์เกี่ยวกับหัวข้อดูที่jQuery a monad - Stack Overflow
nealmcb

1
ดูเพิ่มเติมที่ Google Talk Monads และ Gonads ของดักแคร็กฟอร์ดและโค้ดจาวาสคริปต์ของเขาเพื่อทำการดัดแปลงขยายการทำงานที่คล้ายกันของไลบรารี AJAX และสัญญา: douglascrockford / monad · GitHub
nealmcb


7

Monad เป็นวิธีการรวมการคำนวณเข้าด้วยกันซึ่งใช้บริบทร่วมกัน มันเหมือนกับการสร้างเครือข่ายท่อ เมื่อสร้างเครือข่ายจะไม่มีข้อมูลไหลผ่าน แต่เมื่อฉันเสร็จ piecing บิตทั้งหมดพร้อมกับ 'ผูก' และ 'กลับ' จากนั้นฉันก็เรียกบางอย่างเช่นrunMyMonad monad dataและข้อมูลไหลผ่านท่อ


1
นั่นเป็นเหมือนการใช้งานมากกว่า Monad ด้วย Monads คุณต้องได้รับข้อมูลจากไปป์ก่อนจึงจะสามารถเลือกไปป์ถัดไปเพื่อเชื่อมต่อ
Peaker

ใช่คุณอธิบายการใช้งานไม่ใช่ Monad Monad คือการสร้างส่วนท่อถัดไปในจุดขึ้นอยู่กับข้อมูลที่มาถึงจุดนั้นภายในท่อ
Will Ness

6

ในทางปฏิบัติ monad เป็นการใช้งานที่กำหนดเองของตัวดำเนินการจัดองค์ประกอบฟังก์ชั่นที่ดูแลผลข้างเคียงและอินพุตที่ไม่เข้ากันและค่าส่งคืน (สำหรับการผูกมัด)


5

หากฉันเข้าใจถูกต้อง IEnumerable นั้นมาจาก monads ฉันสงสัยว่าอาจเป็นมุมที่น่าสนใจสำหรับพวกเราจากโลก C # หรือไม่?

สำหรับสิ่งที่คุ้มค่าต่อไปนี้เป็นลิงก์สำหรับบทเรียนที่ช่วยฉัน (และไม่ฉันยังไม่เข้าใจว่า monads คืออะไร)


5

สองสิ่งที่ช่วยฉันได้ดีที่สุดเมื่อเรียนรู้ที่นั่นคือ:

บทที่ 8 "Parsers ฟังก์ชั่น" จากหนังสือเกรแฮมฮัตตันของการเขียนโปรแกรมใน Haskell อันนี้ไม่ได้พูดถึง monads เลยจริง ๆ แต่ถ้าคุณสามารถทำงานผ่านบทและเข้าใจทุกอย่างในนั้นโดยเฉพาะอย่างยิ่งการประเมินลำดับการดำเนินการผูกคุณจะเข้าใจ internals ของ monads คาดว่าสิ่งนี้จะต้องลองหลายครั้ง

กวดวิชาทั้งหมดเกี่ยวกับ Monads นี่เป็นตัวอย่างที่ดีหลายประการเกี่ยวกับการใช้งานและฉันต้องบอกว่าการเปรียบเทียบในภาคผนวกที่ฉันทำงานให้ฉัน


5

ดูเหมือนว่าจะเป็นสิ่งที่ทำให้มั่นใจได้ว่าการดำเนินการทั้งหมดที่กำหนดไว้ใน 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 มิติพื้นที่นั้นไม่สามารถเป็นอะไรก็ได้ยกเว้นความยาวกำลังสอง เราไม่จำเป็นต้องยืนยันเพื่อให้แน่ใจว่าโลกของเราเป็นอย่างที่เป็นอยู่เราเพียงแค่ใช้ความหมายของความเป็นจริงเพื่อป้องกันไม่ให้โปรแกรมของเราหลุดออกนอกเส้นทาง

ฉันค่อนข้างรับประกันได้ว่าจะผิด แต่ฉันคิดว่านี่จะช่วยให้ใครบางคนออกไปที่นั่นดังนั้นหวังว่าจะช่วยให้ใครบางคน


5

ในบริบทของสกาล่าคุณจะพบว่าคำจำกัดความที่ง่ายที่สุดดังต่อไปนี้ โดยทั่วไป 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


5

คำตอบนี้เริ่มต้นด้วยตัวอย่างที่สร้างแรงบันดาลใจทำงานผ่านตัวอย่างมาจากตัวอย่างของ 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 tM u
  • wrapต้องใช้เวลาและการส่งกลับvM v

t, uและvมีสามประเภทที่อาจจะใช่หรือไม่เหมือนกัน Monad เป็นไปตามคุณสมบัติสามประการที่คุณพิสูจน์แล้วสำหรับ monad ของคุณโดยเฉพาะ:

  • การให้อาหารห่อtเข้าไปในฟังก์ชั่นจะเหมือนกับการผ่านtเข้าไปในฟังก์ชั่นที่ยังไม่ได้เปิด

    อย่างเป็นทางการ: feed(f, wrap(x)) = f(x)

  • ให้อาหารM tเข้าไปไม่ได้ทำอะไรไปwrapM 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) และจะเรียกว่าwrapreturn


5

ฉันจะพยายามอธิบายMonadในบริบทของ Haskell

ในการเขียนโปรแกรมเชิงฟังก์ชันองค์ประกอบของฟังก์ชั่นเป็นสิ่งสำคัญ ช่วยให้โปรแกรมของเราประกอบด้วยฟังก์ชั่นขนาดเล็กที่อ่านง่าย

สมมติว่าเรามีสองฟังก์ชั่น: และg :: Int -> Stringf :: String -> Bool

เราสามารถทำได้(f . g) xซึ่งเหมือนกับค่าf (g x)ที่xอยู่ที่ไหนInt

เมื่อทำการจัดองค์ประกอบ / ใช้ผลลัพธ์ของฟังก์ชันหนึ่งกับอีกฟังก์ชันหนึ่งการจับคู่ประเภทเป็นสิ่งสำคัญ ในกรณีดังกล่าวข้างต้นประเภทของผลที่ได้กลับโดยจะต้องเป็นเช่นเดียวกับชนิดรับการยอมรับจากgf

แต่บางครั้งค่าอยู่ในบริบทและสิ่งนี้ทำให้การจัดเรียงประเภทง่ายขึ้นเล็กน้อย (การมีค่าในบริบทมีประโยชน์มากตัวอย่างเช่นMaybe IntประเภทแสดงIntค่าที่อาจไม่มีอยู่IO StringประเภทจะแสดงStringค่าที่มีซึ่งเป็นผลจากการดำเนินการผลข้างเคียงบางอย่าง)

สมมติว่าตอนนี้เรามีและg1 :: Int -> Maybe String และมีความคล้ายคลึงกับและตามลำดับf1 :: String -> Maybe Boolg1f1gf

เราทำไม่ได้(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 เหล่านี้) ไปยังฟังก์ชันที่ไม่คาดหวังค่าในบริบท บริบทจะได้รับการดูแล

หากมีสิ่งหนึ่งที่จะจำได้ว่านี่มันคือMonads ช่วยให้องค์ประกอบของฟังก์ชั่นที่เกี่ยวข้องกับค่าในบริบท


นี่คือการดำเนินการ: github.com/brianspinos777/Programming_cheat_sheets/blob/master/…
Brian Joseph Spinos

IOW Monad เป็นโปรโตคอลการเรียกใช้ฟังก์ชันทั่วไป
Will Ness

คำตอบของคุณมีประโยชน์มากที่สุดในความคิดของฉัน แม้ว่าฉันจะต้องบอกว่าฉันคิดว่าการเน้นจะต้องอยู่บนความจริงที่ว่าฟังก์ชั่นที่คุณอ้างถึงไม่เพียง แต่เกี่ยวข้องกับค่าในบริบทเท่านั้น ตัวอย่างเช่นฟังก์ชั่น f :: ma -> mb จะสามารถเขียนได้อย่างง่ายดายด้วยฟังก์ชั่นอื่น g :: mb -> m c แต่ monads (ผูกเฉพาะ) ช่วยให้เราสามารถเขียนฟังก์ชั่นตลอดเวลาซึ่งทำให้การป้อนข้อมูลของพวกเขาในบริบทเดียวกันโดยไม่ต้องเราจำเป็นต้องใช้ค่าออกจากบริบทที่แรก (ซึ่งมีประสิทธิภาพจะลบข้อมูลออกจากค่า)
เจมส์

@ James ฉันคิดว่าควรจะเน้นฟังก์ชั่น?
Jonas

@ Jonas ฉันเดาว่าฉันไม่ได้อธิบายอย่างเหมาะสม เมื่อฉันพูดว่าฟังก์ชั่นใส่ค่าในบริบทฉันหมายถึงพวกเขามีประเภท (a -> mb) สิ่งเหล่านี้มีประโยชน์มากเนื่องจากการใส่ค่าลงในบริบทจะเป็นการเพิ่มข้อมูลใหม่เข้าไป แต่มักจะเชื่อมโยง (a -> mb) และ a (b -> mc) เข้าด้วยกันเนื่องจากเราไม่สามารถนำค่าออกมาได้ ของบริบท ดังนั้นเราจะต้องใช้กระบวนการที่ซับซ้อนเพื่อเชื่อมโยงฟังก์ชั่นเหล่านี้เข้าด้วยกันอย่างสมเหตุสมผลโดยขึ้นอยู่กับบริบทเฉพาะและพระสงฆ์เพียงแค่อนุญาตให้เราทำสิ่งนี้ในลักษณะที่สอดคล้องกันโดยไม่คำนึงถึงบริบท
James

5

TL; DR

{-# 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เป็นตัวตนทางขวาและซ้าย

Kleisli สามคน

ในการเขียนโปรแกรม 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 มีฟังก์ชั่นที่ใช้อาร์กิวเมนต์หนึ่งและกลับคำนวณเอกam 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 bm 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)

ด้วยประเภทหน่วยที่ใช้แบบอะนาล็อกกับเป็นvoidC

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

Input / Output

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
. . .              . . .

ความไม่บริสุทธิ์ของรหัสที่ใช้IOprimitives ถูกโพรโทคอลอย่างถาวรโดยระบบชนิด เพราะความบริสุทธิ์นั้นยอดเยี่ยมสิ่งที่เกิดขึ้น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 ต้องรักษาตัวตนซึ่มส์และองค์ประกอบที่“โครงสร้าง” ของในCHomC(X, Y)X -> YCCD

                    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

แต่ทำไมทฤษฎีควรให้นามธรรมใช้สำหรับการเขียนโปรแกรมใด ๆ

คำตอบนั้นง่าย: ในฐานะนักวิทยาศาสตร์คอมพิวเตอร์เราเห็นคุณค่าของความเป็นนามธรรม ! เมื่อเราออกแบบส่วนต่อประสานกับส่วนประกอบซอฟต์แวร์เราต้องการให้มันเปิดเผยน้อยที่สุดเกี่ยวกับการใช้งาน เราต้องการที่จะสามารถแทนที่การใช้งานด้วยทางเลือกมากมาย 'อินสแตนซ์' อื่น ๆ ของ 'แนวคิด' เดียวกัน เมื่อเราออกแบบอินเทอร์เฟซทั่วไปให้กับไลบรารีโปรแกรมจำนวนมากสิ่งสำคัญยิ่งกว่าคืออินเตอร์เฟสที่เราเลือกนั้นมีการใช้งานที่หลากหลาย มันเป็นความคิดทั่วไปของแนวคิด monad ที่เราให้คุณค่าอย่างมากมันเป็นเพราะทฤษฎีหมวดหมู่นั้นเป็นนามธรรมดังนั้นแนวคิดของมันจึงมีประโยชน์สำหรับการเขียนโปรแกรม

มันแทบจะไม่น่าแปลกใจเลยที่การวางหลักเกณฑ์ทั่วไปของพระที่เรานำเสนอไว้ด้านล่างนี้ยังมีความสัมพันธ์ใกล้ชิดกับทฤษฎีหมวดหมู่ แต่เราเน้นว่าจุดประสงค์ของเรานั้นใช้ได้จริงมากไม่ใช่เพื่อ 'ใช้ทฤษฎีหมวดหมู่' แต่เป็นการหาวิธีทั่วไปในการจัดโครงสร้างไลบรารี combinator เป็นเพียงความโชคดีของเราที่นักคณิตศาสตร์ได้ทำงานมามากมายสำหรับเรา!

จากGeneralising Monads ถึง Arrowsโดย John Hughes


4

สิ่งที่โลกต้องการคือการโพสต์บล็อก monad อื่น แต่ฉันคิดว่านี่เป็นประโยชน์ในการระบุ monads ที่มีอยู่ในป่า

สามเหลี่ยม Sierpinski

ด้านบนเป็นเศษส่วนที่เรียกว่าสามเหลี่ยม Sierpinski ซึ่งเป็นเศษส่วนเดียวที่ฉันจำได้ เศษส่วนเป็นโครงสร้างที่คล้ายตัวเองเช่นสามเหลี่ยมด้านบนซึ่งชิ้นส่วนมีความคล้ายคลึงกับทั้งหมด (ในกรณีนี้ครึ่งหนึ่งของมาตราส่วนเท่ากับสามเหลี่ยมหลัก)

Monads เป็น fractals เมื่อกำหนดโครงสร้างข้อมูลแบบ monadic ค่าของมันจะสามารถสร้างเป็นค่าอื่นของโครงสร้างข้อมูลได้ นี่คือเหตุผลที่มีประโยชน์กับการเขียนโปรแกรมและนี่คือสาเหตุที่มันเกิดขึ้นในหลาย ๆ สถานการณ์


3
คุณหมายถึง "สิ่งที่โลกไม่ต้องการ ... "? การเปรียบเทียบที่ดี!
groverboy

@ icc97 คุณพูดถูก - ความหมายชัดเจนเพียงพอ Sarcasm ไม่ได้ตั้งใจขอโทษผู้เขียน
groverboy

สิ่งที่โลกต้องการคืออีกกระทู้แสดงความคิดเห็นที่เป็นการยืนยันการเสียดสี แต่ถ้าอ่านอย่างระมัดระวังฉันได้เขียนแต่ควรทำให้ชัดเจน
ยูจีนโยโกตะ

4

http://code.google.com/p/monad-tutorial/เป็นงานที่อยู่ระหว่างดำเนินการเพื่อตอบคำถามนี้อย่างแน่นอน


5
ดูว่าสิ่งนี้ช่วยให้projects.tmorris.net/public/what-does-monad-mean/artifacts/1.1/…
Tony Morris

Google Code จะปิดตัวลงในวันที่ 2016-01-15 โครงการส่วนใหญ่เป็นแบบอ่านอย่างเดียวตั้งแต่วันที่ 2015-08-25
Peter Mortensen

4

ให้ด้านล่าง{| 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เว้นแต่จะรู้ว่าผลลัพธ์ในที่สุดมีบริบทประเภทเดียวกัน

มีพระอื่น ๆ ที่ใช้แทนบริบทที่แตกต่างกัน นี่คือลักษณะของอีกไม่กี่ IOmonad ไม่จริงมีaแต่ก็รู้ว่าผู้ชายและจะได้รับว่าaสำหรับคุณ State stmonad มีที่ซ่อนความลับของstว่ามันจะส่งผ่านไปfใต้โต๊ะแม้ว่าเพิ่งมาขอf monad คล้ายกับแม้ว่ามันเพียง แต่ช่วยให้ดูที่aReader rState stfr

ประเด็นทั้งหมดนี้คือข้อมูลประเภทใดก็ตามที่ประกาศตัวเองว่าเป็น Monad กำลังประกาศบริบทบางอย่างรอบ ๆ การดึงค่าออกมาจาก monad กำไรที่ยิ่งใหญ่จากทั้งหมดนี้? มันง่ายพอที่จะรองรับการคำนวณด้วยบริบทบางอย่าง มันสามารถทำให้เกิดความยุ่งเหยิงอย่างไรก็ตามเมื่อคบกันหลาย ๆ การคำนวณที่รับภาระ การดำเนินการ monad ดูแลการแก้ไขการโต้ตอบของบริบทเพื่อให้โปรแกรมเมอร์ไม่จำเป็นต้อง

หมายเหตุการใช้ที่ลดระเบียบโดยโดยการบางอย่างของตนเองออกไปจาก>>= fนั่นคือในกรณีข้างต้นNothingตัวอย่างเช่นfไม่ได้รับการตัดสินใจว่าจะทำอย่างไรในกรณีของNothing; >>=มันเข้ารหัสใน นี่คือการแลกเปลี่ยน ถ้ามันเป็นสิ่งที่จำเป็นสำหรับfการตัดสินใจว่าจะทำอย่างไรในกรณีของNothingแล้วfควรจะได้รับฟังก์ชั่นจากไปMaybe a Maybe bในกรณีนี้Maybeการเป็น monad นั้นไม่เกี่ยวข้อง

อย่างไรก็ตามโปรดทราบว่าบางครั้งประเภทข้อมูลไม่ส่งออกมันเป็นคอนสตรัคเตอร์ (ดูที่คุณ IO) และถ้าเราต้องการทำงานกับค่าโฆษณาที่เรามีทางเลือกน้อย


3

Monad เป็นสิ่งที่ใช้ห่อหุ้มวัตถุที่เปลี่ยนสถานะ มักพบในภาษาที่ไม่อนุญาตให้คุณแก้ไขสถานะได้ (เช่น Haskell)

ตัวอย่างจะใช้สำหรับไฟล์ I / O

คุณจะสามารถใช้ monad สำหรับไฟล์ I / O เพื่อแยกลักษณะการเปลี่ยนสถานะเป็นเพียงรหัสที่ใช้ Monad รหัสใน Monad สามารถเพิกเฉยต่อสภาพการเปลี่ยนแปลงของโลกภายนอก Monad ได้อย่างมีประสิทธิภาพซึ่งทำให้ง่ายขึ้นมากที่จะให้เหตุผลเกี่ยวกับผลกระทบโดยรวมของโปรแกรมของคุณ


3
อย่างที่ฉันเข้าใจพระก็เป็นมากกว่านั้น การห่อหุ้มสถานะที่ไม่แน่นอนในภาษาที่ใช้งานได้ "บริสุทธิ์" เป็นแอปพลิเคชันเดียวของ monads
thSoft
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.