मान लीजिए कि एक फ़ंक्शन के साइड इफेक्ट्स हैं। यदि हम इनपुट और आउटपुट मापदंडों के रूप में उत्पन्न होने वाले सभी प्रभावों को लेते हैं, तो फ़ंक्शन बाहरी दुनिया के लिए शुद्ध है।
तो, एक अशुद्ध कार्य के लिए
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
से शुद्ध है। हम एक पैरामीरिज्ड डेटा प्रकार को परिभाषित करते हैं type IO a = RealWorld -> (a, RealWorld)
, इसलिए हमें कई बार RealWorld टाइप करने की आवश्यकता नहीं है, और बस लिख सकते हैं
f :: Int -> IO Int
प्रोग्रामर के लिए, एक रियलवर्ल्ड को सीधे हैंडल करना बहुत खतरनाक है - विशेष रूप से, अगर किसी प्रोग्रामर को रियलवर्ल्ड के प्रकार के मूल्य पर अपने हाथ मिलते हैं, तो वे इसे कॉपी करने की कोशिश कर सकते हैं , जो मूल रूप से असंभव है। (उदाहरण के लिए, संपूर्ण फाइलसिस्टम को कॉपी करने की कोशिश करने के बारे में सोचें। आप इसे कहां रखेंगे?) इसलिए, 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)
)
हम सभी भिक्षुओं के साथ काम कर रहे थे
अब देखते हैं कि हमने क्या किया है:
- हमने एक ऑपरेटर को परिभाषित किया है
(~~~) :: IO b -> (b -> IO c) -> IO c
जो दो अशुद्ध कार्यों को एक साथ जोड़ता है
- हमने एक फ़ंक्शन को परिभाषित किया है
impurify :: a -> IO a
जो अशुद्ध करने के लिए एक शुद्ध मूल्य को परिवर्तित करता है।
अब हम पहचान बनाने (>>=) = (~~~)
और return = impurify
, और देखते हैं? हमें एक सन्यासी मिला है।
तकनीकी नोट
यह सुनिश्चित करने के लिए कि यह वास्तव में एक सनक है, अभी भी कुछ स्वयंसिद्ध हैं जिन्हें भी जाँचने की आवश्यकता है:
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
व्यायाम के रूप में छोड़ दिया।