เหตุใดผลข้างเคียงที่จำลองเป็นพระใน Haskell


172

ใครบ้างให้คำแนะนำว่าทำไมการคำนวณที่ไม่บริสุทธิ์ใน Haskell ถูกจำลองเป็นพระ?

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


15
Monads เพียงกำหนดสองการดำเนินงาน
Dario

3
แต่แล้วผลตอบแทนและความล้มเหลวล่ะ (นอกจาก (>>) และ (>> =))
bodacydo

55
สองการดำเนินงานและreturn เป็นเช่นเดียวกับ(เช่นจะละเว้นผลลัพธ์ของอาร์กิวเมนต์แรก) เราไม่ได้พูดคุยเกี่ยวกับ (>>=)x >> yx >>= \\_ -> yfail
porges

2
@Porges ทำไมไม่พูดเกี่ยวกับความล้มเหลว มันค่อนข้างมีประโยชน์ในบางที, Parser, ฯลฯ
ทางเลือก

16
@monadic: failอยู่ในMonadชั้นเรียนเนื่องจากอุบัติเหตุทางประวัติศาสตร์ MonadPlusจริงๆมันอยู่ใน โปรดทราบว่าคำจำกัดความเริ่มต้นของมันไม่ปลอดภัย
JB

คำตอบ:


292

สมมติว่าฟังก์ชั่นมีผลข้างเคียง หากเราใช้เอฟเฟกต์ทั้งหมดที่สร้างขึ้นเป็นพารามิเตอร์อินพุทและเอาท์พุทฟังก์ชั่นนั้นบริสุทธิ์ต่อโลกภายนอก

ดังนั้นสำหรับฟังก์ชั่นที่ไม่บริสุทธิ์

f' :: Int -> Int

เราเพิ่ม RealWorld เข้ากับการพิจารณา

f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the side effects,
-- then return the new world.

จากนั้นfบริสุทธิ์อีกครั้ง เรากำหนดประเภทข้อมูลแบบ Parametrized type IO a = RealWorld -> (a, RealWorld)ดังนั้นเราไม่จำเป็นต้องพิมพ์ RealWorld หลายครั้งและสามารถเขียนได้

f :: Int -> IO Int

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

องค์ประกอบของฟังก์ชั่น "ไม่บริสุทธิ์"

ฟังก์ชั่นที่ไม่บริสุทธิ์เหล่านี้ไร้ประโยชน์ถ้าเราไม่สามารถโยงมันเข้าด้วยกัน พิจารณา

getLine     :: IO String            ~            RealWorld -> (String, RealWorld)
getContents :: String -> IO String  ~  String -> RealWorld -> (String, RealWorld)
putStrLn    :: String -> IO ()      ~  String -> RealWorld -> ((),     RealWorld)

เราต้องการ

  • รับชื่อไฟล์จากคอนโซล
  • อ่านไฟล์นั้นและ
  • พิมพ์เนื้อหาของไฟล์นั้นไปยังคอนโซล

เราจะทำอย่างไรถ้าเราสามารถเข้าถึงรัฐในโลกแห่งความจริงได้?

printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
                       (contents, world2) = (getContents filename) world1 
                   in  (putStrLn contents) world2 -- results in ((), world3)

เราเห็นรูปแบบที่นี่ ฟังก์ชั่นนี้เรียกว่า:

...
(<result-of-f>, worldY) = f               worldX
(<result-of-g>, worldZ) = g <result-of-f> worldY
...

ดังนั้นเราสามารถกำหนดโอเปอเรเตอร์~~~เพื่อผูกมัน:

(~~~) :: (IO b) -> (b -> IO c) -> IO c

(~~~) ::      (RealWorld -> (b,   RealWorld))
      ->                    (b -> RealWorld -> (c, RealWorld))
      ->      (RealWorld                    -> (c, RealWorld))
(f ~~~ g) worldX = let (resF, worldY) = f worldX
                   in g resF worldY

จากนั้นเราก็สามารถเขียน

printFile = getLine ~~~ getContents ~~~ putStrLn

โดยไม่ต้องสัมผัสโลกแห่งความจริง

"Impurification"

ตอนนี้สมมติว่าเราต้องการสร้างเนื้อหาไฟล์เป็นตัวพิมพ์ใหญ่เช่นกัน การพิมพ์ใหญ่เป็นหน้าที่บริสุทธิ์

upperCase :: String -> String

IO Stringแต่การที่จะทำให้มันกลายเป็นโลกแห่งความจริงก็มีการส่งคืน มันง่ายที่จะยกฟังก์ชั่นดังกล่าว:

impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)

นี่สามารถสรุปได้:

impurify :: a -> IO a

impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)

เพื่อให้impureUpperCase = impurify . upperCaseและเราสามารถเขียน

printUpperCaseFile = 
    getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn

(หมายเหตุ: โดยปกติเราเขียนgetLine ~~~ getContents ~~~ (putStrLn . upperCase))

เราทำงานกับ monads มาตลอด

ตอนนี้เรามาดูสิ่งที่เราทำ:

  1. เราได้นิยามโอเปอเรเตอร์(~~~) :: IO b -> (b -> IO c) -> IO cที่เชื่อมโยงฟังก์ชันที่ไม่บริสุทธิ์สองอย่างเข้าด้วยกัน
  2. เรากำหนดฟังก์ชันimpurify :: a -> IO aที่แปลงค่าบริสุทธิ์ให้ไม่บริสุทธิ์

ตอนนี้เราทำบัตรประจำตัว(>>=) = (~~~)และreturn = impurifyและดู? เรามี monad


หมายเหตุด้านเทคนิค

เพื่อให้แน่ใจว่าเป็น monad จริงๆยังมีสัจพจน์บางอย่างที่ต้องตรวจสอบด้วย:

  1. return a >>= f = f a

     impurify a                =  (\world -> (a, world))
    (impurify a ~~~ f) worldX  =  let (resF, worldY) = (\world -> (a, world )) worldX 
                                  in f resF worldY
                               =  let (resF, worldY) =            (a, worldX)       
                                  in f resF worldY
                               =  f a worldX
  2. f >>= return = f

    (f ~~~ impurify) worldX  =  let (resF, worldY) = f worldX 
                                in impurify resF worldY
                             =  let (resF, worldY) = f worldX      
                                in (resF, worldY)
                             =  f worldX
  3. f >>= (\x -> g x >>= h) = (f >>= g) >>= h

    ทิ้งไว้เป็นการออกกำลังกาย


5
+1 แต่ฉันต้องการที่จะทราบว่านี้ครอบคลุมเฉพาะกรณี IO blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.htmlคล้ายกันมาก แต่พูดถึงเรื่องทั่วไปแล้วRealWorld... คุณจะเห็น
ephemient

4
โปรดทราบว่าคำอธิบายนี้ไม่สามารถนำไปใช้กับ Haskell ได้จริง ๆIOเพราะสนับสนุนการโต้ตอบการทำงานพร้อมกันและการไม่ยอมแพ้ ดูคำตอบของฉันสำหรับคำถามนี้สำหรับตัวชี้เพิ่มเติม
Conal

2
@Conal GHC ใช้งานจริงIOด้วยวิธีนี้ แต่RealWorldไม่ได้แสดงถึงโลกแห่งความจริงเป็นเพียงโทเค็นเพื่อให้การดำเนินการเป็นไปตามลำดับ ("เวทย์มนตร์" คือสิ่งที่RealWorldเป็นเอกลักษณ์เฉพาะของ GHC Haskell)
Jeremy List

2
@JeremyList ตามที่ผมเข้าใจมันดำเนิน GHC IOผ่านการรวมกันของการแสดงนี้และเรียบเรียงมายากลที่ไม่ได้มาตรฐาน (รำลึกของไวรัส C คอมไพเลอร์ที่มีชื่อเสียงเคน ธ อมป์สัน ) สำหรับประเภทอื่น ๆ ความจริงอยู่ในซอร์สโค้ดพร้อมกับความหมายของ Haskell ปกติ
Conal

1
@Clonal ความคิดเห็นของฉันเป็นเพราะฉันได้อ่านส่วนที่เกี่ยวข้องของรหัสที่มา GHC
รายการเจเรมี

43

ใครบ้างให้คำแนะนำว่าทำไมการคำนวณแบบไม่บริสุทธิ์ใน Haskell ถูกจำลองเป็นพระ?

คำถามนี้มีความเข้าใจผิดอย่างกว้างขวาง มลทินและ Monad เป็นแนวคิดอิสระ สิ่งเจือปนไม่ได้เป็นแบบจำลองโดย Monad ค่อนข้างมีชนิดข้อมูลบางอย่างเช่นIOที่แสดงถึงการคำนวณที่จำเป็น และสำหรับบางประเภทนั้นเศษส่วนเล็ก ๆ ของอินเตอร์เฟสของพวกเขาสอดคล้องกับรูปแบบอินเตอร์เฟสที่เรียกว่า "Monad" นอกจากนี้ยังมีเป็นที่รู้จักกันไม่มีคำอธิบายที่บริสุทธิ์ / ทำงาน / denotative ของIO(และมีไม่น่าจะเป็นหนึ่งพิจารณา"บาปถัง"วัตถุประสงค์ของการIO) แม้ว่าจะมีเรื่องบอกกันทั่วไปเกี่ยวกับการเป็นความหมายของWorld -> (a, World) IO aเรื่องนั้นไม่สามารถอธิบายได้จริงIOเพราะIOรองรับการเกิดพร้อมกันและ nondeterminism เรื่องราวไม่ได้ทำงานแม้กระทั่งสำหรับการคำนวณแบบกำหนดค่าที่อนุญาตให้มีการโต้ตอบระดับกลางกับโลก

สำหรับคำอธิบายเพิ่มเติมดูคำตอบนี้

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


1
@ KennyTM: แต่RealWorldก็อย่างที่เอกสารพูดว่า "มนต์วิเศษ" เป็นโทเค็นที่แสดงถึงสิ่งที่ระบบรันไทม์กำลังทำอยู่มันไม่ได้มีความหมายอะไรเกี่ยวกับโลกแห่งความเป็นจริง คุณไม่สามารถคิดสร้างใหม่เพื่อสร้าง "ด้าย" โดยไม่ต้องใช้กลอุบายพิเศษ วิธีการที่ไร้เดียงสาจะสร้างสิ่งเดียวบล็อกการกระทำที่มีความคลุมเครือมากมายเกี่ยวกับเวลาที่มันจะทำงาน
CA McCann

4
นอกจากนี้ฉันจะยืนยันว่าพระมีความจำเป็นอย่างยิ่งในธรรมชาติ หาก functor แสดงโครงสร้างบางอย่างที่มีค่าฝังอยู่อินสแตนซ์ monad หมายความว่าคุณสามารถสร้างและแผ่เลเยอร์ใหม่ตามค่าเหล่านั้นได้ ดังนั้นความหมายใดก็ตามที่คุณกำหนดให้กับเลเยอร์ของนักแสดงเดี่ยว Monad หมายความว่าคุณสามารถสร้างจำนวนเลเยอร์ที่ไม่ จำกัด ด้วยความคิดที่เข้มงวดเกี่ยวกับเวรกรรมจากหนึ่งไปยังอีก อินสแตนซ์ที่เฉพาะเจาะจงอาจไม่มีโครงสร้างที่จำเป็นอย่างแท้จริง แต่Monadโดยทั่วไปแล้วมี
CA McCann

3
โดย " Monadทั่วไป" ฉันหมายถึงอย่างคร่าวๆforall m. Monad m => ...คือทำงานกับอินสแตนซ์โดยพลการ สิ่งที่คุณสามารถทำกับ monad โดยพลการเกือบจะเหมือนกับสิ่งที่คุณสามารถทำได้IO: รับทึบดั้งเดิม (เป็นฟังก์ชั่นอาร์กิวเมนต์หรือจากห้องสมุดตามลำดับ) สร้าง no-ops ด้วยreturnหรือเปลี่ยนค่าในลักษณะที่ไม่สามารถย้อนกลับได้โดยใช้(>>=). สาระสำคัญของการเขียนโปรแกรมใน monad โดยพลการคือการสร้างรายชื่อของการกระทำที่เอาคืนไม่ได้: "do X จากนั้นทำ Y จากนั้น ... " ฟังดูจำเป็นสำหรับฉัน!
CA McCann

2
ไม่คุณยังพลาดจุดของฉันที่นี่ แน่นอนว่าคุณจะไม่ใช้ความคิดนั้นกับประเภทเฉพาะเหล่านั้นเพราะมันมีโครงสร้างที่ชัดเจนและมีความหมาย เมื่อฉันพูดว่า "monads arbitrary" ฉันหมายถึง "คุณไม่ได้เลือกอันไหน"; มุมมองที่นี่มาจากภายในตัววัดดังนั้นการคิดmว่าอัตถิภาวนิยมอาจมีประโยชน์มากกว่า นอกจากนี้ "การตีความ" ของฉันยังเป็นการใช้ถ้อยคำใหม่ตามกฎหมาย; รายการคำสั่ง "do X" นั้นเป็น monoid อิสระที่แม่นยำบนโครงสร้างที่ไม่รู้จักที่สร้างขึ้นผ่านทาง(>>=); และกฎหมาย monad เป็นเพียงกฎหมาย monoid ในองค์ประกอบ endofunctor
CA McCann

3
กล่าวโดยสรุปขอบเขตที่ยิ่งใหญ่ที่สุดของสิ่งที่พระสงฆ์ทั้งหมดอธิบายร่วมกันคือการเดินขบวนและไร้ความหมายในอนาคต IOเป็นกรณีทางพยาธิวิทยาอย่างแม่นยำเพราะไม่มีอะไรมากไปกว่าขั้นต่ำนี้ ในบางกรณีประเภทอาจเปิดเผยโครงสร้างมากขึ้นและทำให้มีความหมายที่แท้จริง; แต่อย่างอื่นคุณสมบัติที่สำคัญของ monad - บนพื้นฐานของกฎหมาย - เป็นถือเป็น denotation ชัดเจนเป็นIOมี หากไม่มีการส่งออกคอนสตรัคเตอร์การระบุการกระทำดั้งเดิมหรือสิ่งที่คล้ายกันอย่างละเอียดถี่ถ้วนสถานการณ์ก็สิ้นหวัง
CA McCann

13

ตามที่ฉันเข้าใจแล้วมีคนชื่อEugenio Moggiแรกสังเกตว่าการสร้างทางคณิตศาสตร์ที่คลุมเครือก่อนหน้านี้เรียกว่า "monad" สามารถใช้เพื่อจำลองผลข้างเคียงในภาษาคอมพิวเตอร์และด้วยเหตุนี้จึงระบุความหมายของพวกเขาโดยใช้ เมื่อ Haskell ได้รับการพัฒนามีหลายวิธีในการคำนวณแบบไม่บริสุทธิ์ (ดูรายละเอียดเพิ่มเติมจาก"ผมเสื้อเชิ้ต" ของ Simon Peyton Jones ) แต่เมื่อ Phil Wadler ได้แนะนำพระอย่างรวดเร็วกลายเป็นที่ชัดเจนว่านี่คือคำตอบ และที่เหลือคือประวัติศาสตร์


3
ไม่มาก เป็นที่ทราบกันดีว่า monad สามารถจำลองการตีความเป็นเวลานาน (อย่างน้อยก็ตั้งแต่ "Topoi: การวิเคราะห์หมวดหมู่แบบลอจิก) ในทางกลับกันมันเป็นไปไม่ได้ที่จะแสดงประเภทของ monad อย่างชัดเจน ภาษามารอบ ๆ และจากนั้น Moggi ใส่สองและสองเข้าด้วยกัน
nomen

1
บางทีพระอาจจะง่ายต่อการเข้าใจถ้าพวกเขาถูกกำหนดในแง่ของการห่อแผนที่และแกะด้วยกลับมาเป็นคำพ้องสำหรับการตัด
aoeu256

9

ใครบ้างให้คำแนะนำว่าทำไมการคำนวณแบบไม่บริสุทธิ์ใน Haskell ถูกจำลองเป็นพระ?

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

ซึ่งหมายความว่าคุณจะต้องจบด้วยประเภทIO aที่คำนวณแบบไม่บริสุทธิ์ จากนั้นคุณจำเป็นต้องรู้วิธีการรวมการคำนวณเหล่านี้ซึ่งใช้ในลำดับ ( >>=) และการเพิ่มค่า ( return) เป็นวิธีที่ชัดเจนที่สุดและพื้นฐาน

ด้วยสองสิ่งนี้คุณได้กำหนด monad ไว้แล้ว (โดยไม่ต้องคิดเลย);)

นอกจากนี้ monads ให้แนวคิดทั่วไปมากและมีประสิทธิภาพหลายชนิดเพื่อให้การควบคุมการไหลสามารถทั่วไปเพื่อความสะดวกในการทำงานเช่นเอกsequence, liftMหรือไวยากรณ์พิเศษทำให้ unpureness ไม่ได้เช่นกรณีพิเศษ

ดูmonads ในการเขียนโปรแกรมการทำงานและการพิมพ์ที่ไม่ซ้ำกัน (ทางเลือกเดียวที่ฉันรู้) สำหรับข้อมูลเพิ่มเติม


6

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

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

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

มีโครงสร้างที่ซับซ้อนกว่านี้ไหม? แน่นอน! เราสามารถแบ่งผลข้างเคียงที่เป็นไปได้ออกมาเป็นผลกระทบที่อิงกับระบบไฟล์เอฟเฟกต์บนเครือข่ายและอื่น ๆ อีกมากมายและเราสามารถสร้างกฎการแต่งเพลงที่ซับซ้อนยิ่งขึ้นซึ่งรักษารายละเอียดเหล่านี้ไว้ แต่มันกลับลงมาอีกครั้ง: Monadเรียบง่ายและมีประสิทธิภาพเพียงพอที่จะแสดงคุณสมบัติส่วนใหญ่ที่เราใส่ใจ (โดยเฉพาะอย่างยิ่งการเชื่อมโยงและสัจพจน์อื่น ๆ ให้เราทดสอบแอปพลิเคชันของเราเป็นชิ้นเล็ก ๆ ด้วยความมั่นใจว่าผลข้างเคียงของแอปพลิเคชันแบบรวมจะเหมือนกับการรวมกันของผลข้างเคียงของชิ้นส่วน)


4

จริงๆแล้วมันค่อนข้างเป็นวิธีที่สะอาดในการคิดถึง I / O ในลักษณะที่ใช้งานได้

ในภาษาการเขียนโปรแกรมส่วนใหญ่คุณทำการอินพุต / เอาต์พุต ใน Haskell ลองนึกภาพการเขียนรหัสไม่ต้องทำการดำเนินการ แต่เพื่อสร้างรายการการดำเนินการที่คุณต้องการทำ

Monads เป็นเพียงแค่รูปแบบที่สวยงาม

หากคุณต้องการทราบว่าทำไมพระสงฆ์ต่างจากสิ่งอื่นฉันเดาคำตอบว่ามันเป็นวิธีการทำงานที่ดีที่สุดในการเป็นตัวแทน I / O ที่ผู้คนสามารถนึกถึงเมื่อพวกเขากำลังสร้าง Haskell


3

AFAIK เหตุผลคือสามารถรวมการตรวจสอบผลข้างเคียงในระบบชนิด หากคุณต้องการทราบเพิ่มเติมให้ฟังตอนSE-Radioเหล่านั้น: ตอนที่ 108: Simon Peyton Jones เกี่ยวกับการเขียนโปรแกรมเชิงฟังก์ชั่นและ Haskell ตอนที่ 72: Erik Meijer บน LINQ


2

ข้างต้นมีคำตอบที่ดีมากกับพื้นหลังทางทฤษฎี แต่ฉันต้องการให้มุมมองของฉันใน IO monad ฉันไม่ได้มีประสบการณ์โปรแกรมเมอร์ Haskell ดังนั้นอาจเป็นว่าไร้เดียงสาหรือผิด แต่ฉันช่วยฉันจัดการกับ IO monad ในระดับหนึ่ง (โปรดทราบว่ามันไม่เกี่ยวข้องกับ monads อื่น ๆ )

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

ดังนั้นเราจึงต้องการให้ภาษาของเรา (ฮาเซล) บริสุทธิ์ แต่เราต้องการการใช้งานอินพุต / เอาท์พุตเนื่องจากไม่มีโปรแกรมของเราจะไม่มีประโยชน์ และการดำเนินการเหล่านั้นไม่สามารถบริสุทธิ์ได้โดยธรรมชาติ ดังนั้นวิธีเดียวที่จะจัดการกับเรื่องนี้เราต้องแยกการดำเนินการที่ไม่บริสุทธิ์ออกจากส่วนที่เหลือของรหัส

ที่นี่ monad มา ที่จริงแล้วฉันไม่แน่ใจว่าไม่มีสิ่งก่อสร้างอื่นที่มีคุณสมบัติที่ต้องการเหมือนกัน แต่ประเด็นคือ monad มีคุณสมบัติเหล่านี้ดังนั้นจึงสามารถใช้งานได้ (และใช้สำเร็จ) คุณสมบัติหลักคือเราไม่สามารถหนีจากมันได้ อินเทอร์เฟซ Monad ไม่มีการดำเนินการเพื่อกำจัด monad รอบค่าของเรา Monads (ไม่ใช่ IO) อื่น ๆ จัดเตรียมการดำเนินการดังกล่าวและอนุญาตการจับคู่รูปแบบ (เช่นอาจ) แต่การดำเนินการเหล่านั้นไม่ได้อยู่ในส่วนต่อประสาน Monad คุณสมบัติที่จำเป็นอีกประการหนึ่งคือความสามารถในการปฏิบัติการลูกโซ่

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

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

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

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


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

ความคิดเห็นจาก aoeu256 นี้คือ "สาเหตุ" ที่หายไปในคำอธิบายทั้งหมดที่ให้ไว้จนถึงตอนนี้ (เช่น: monads ไม่ใช่สำหรับมนุษย์ แต่สำหรับคอมไพเลอร์)
João Otero
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.