สมมติว่าฟังก์ชั่นมีผลข้างเคียง หากเราใช้เอฟเฟกต์ทั้งหมดที่สร้างขึ้นเป็นพารามิเตอร์อินพุทและเอาท์พุทฟังก์ชั่นนั้นบริสุทธิ์ต่อโลกภายนอก
ดังนั้นสำหรับฟังก์ชั่นที่ไม่บริสุทธิ์
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 มาตลอด
ตอนนี้เรามาดูสิ่งที่เราทำ:
- เราได้นิยามโอเปอเรเตอร์
(~~~) :: IO b -> (b -> IO c) -> IO c
ที่เชื่อมโยงฟังก์ชันที่ไม่บริสุทธิ์สองอย่างเข้าด้วยกัน
- เรากำหนดฟังก์ชัน
impurify :: a -> IO a
ที่แปลงค่าบริสุทธิ์ให้ไม่บริสุทธิ์
ตอนนี้เราทำบัตรประจำตัว(>>=) = (~~~)
และreturn = impurify
และดู? เรามี monad
หมายเหตุด้านเทคนิค
เพื่อให้แน่ใจว่าเป็น monad จริงๆยังมีสัจพจน์บางอย่างที่ต้องตรวจสอบด้วย:
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
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
f >>= (\x -> g x >>= h) = (f >>= g) >>= h
ทิ้งไว้เป็นการออกกำลังกาย