हमें भिक्षुओं की आवश्यकता क्यों है?


366

मेरी विनम्र राय में प्रसिद्ध प्रश्न "एक सन्यासी क्या है?" विशेष रूप से सबसे अधिक मतदान करने वाले, यह स्पष्ट करने की कोशिश करते हैं कि एक सन्यासी है जो स्पष्ट रूप से समझाए बिना कि भिक्षु वास्तव में क्यों आवश्यक हैं । क्या उन्हें किसी समस्या के समाधान के रूप में समझाया जा सकता है?




4
आपने पहले से क्या शोध किया है? कहां देखा है? आपको क्या संसाधन मिले हैं? हम उम्मीद करते हैं कि आप पूछने से पहले एक महत्वपूर्ण मात्रा में अनुसंधान कर सकते हैं, और हमें इस प्रश्न में दिखा सकते हैं कि आपने क्या शोध किया है । ऐसे कई संसाधन हैं जो संसाधनों के लिए प्रेरणा को समझाने की कोशिश करते हैं - यदि आपको कोई भी नहीं मिला है, तो आपको थोड़ा और शोध करने की आवश्यकता हो सकती है। यदि आपने कुछ पाया है, लेकिन उन्होंने आपकी मदद नहीं की, तो यह एक बेहतर प्रश्न है यदि आपने समझाया कि आपने क्या पाया है और विशेष रूप से वे आपके लिए काम क्यों नहीं करते हैं।
DW

8
यह निश्चित रूप से Programmers.StackExchange के लिए एक बेहतर फिट है और StackOverflow के लिए एक अच्छा फिट नहीं है। अगर मैं कर सकता था, तो मैं मतदान करूँगा, लेकिन मैं नहीं कर सकता। = (
jpmc26

3
@ jpmc26 सबसे अधिक संभावना है कि यह "मुख्य रूप से राय-आधारित" के रूप में वहाँ बंद हो जाएगा; यहाँ यह कम से कम एक मौका खड़ा है (जैसा कि
अपवित्रों

जवाबों:


580

हमें भिक्षुओं की आवश्यकता क्यों है?

  1. हम केवल फ़ंक्शन का उपयोग करके प्रोग्राम करना चाहते हैं । ("कार्यात्मक प्रोग्रामिंग (एफपी)" सब के बाद)।
  2. फिर, हमारे पास पहली बड़ी समस्या है। यह एक कार्यक्रम है:

    f(x) = 2 * x

    g(x,y) = x / y

    हम कैसे कह सकते हैं कि सबसे पहले क्या निष्पादित किया जाना है ? हम कार्यों से अधिक नहीं का उपयोग करके कार्यों का क्रमबद्ध क्रम (यानी एक कार्यक्रम ) कैसे बना सकते हैं ?

    समाधान: कार्यों की रचना । यदि आप पहले gऔर फिर चाहते हैं f, तो बस लिखें f(g(x,y))। इस तरह, "कार्यक्रम" एक समारोह के रूप में अच्छी तरह से है: main = f(g(x,y))। ठीक है, पर ...

  3. अधिक समस्याएं: कुछ कार्य विफल हो सकते हैं (यानी g(2,0), 0 से विभाजित करें)। हमारे पास एफपी में कोई "अपवाद" नहीं है (एक अपवाद एक फ़ंक्शन नहीं है)। हम इसे कैसे हल करेंगे?

    समाधान: आइए फ़ंक्शंस को दो तरह की चीज़ों को वापस करने की अनुमति दें : होने के बजाय g : Real,Real -> Real(दो रीयल से वास्तविक में फ़ंक्शन), आइए अनुमति दें g : Real,Real -> Real | Nothing(दो रियल से रियल (कुछ भी नहीं) में फ़ंक्शन करें)।

  4. लेकिन फ़ंक्शंस (सरल होने के लिए) केवल एक ही चीज़ वापस करना चाहिए ।

    समाधान: आइए लौटाए जाने के लिए एक नए प्रकार का डेटा बनाते हैं, एक " बॉक्सिंग प्रकार " जो कि एक वास्तविक को घेरता है या बस कुछ भी नहीं है। इसलिए, हम कर सकते हैं g : Real,Real -> Maybe Real। ठीक है, पर ...

  5. अब क्या होता है f(g(x,y))? fउपभोग करने के लिए तैयार नहीं है Maybe Real। और, हम प्रत्येक फ़ंक्शन को बदलना नहीं चाहते हैं जिसे हम gउपभोग करने के लिए जोड़ सकते हैं Maybe Real

    समाधान: चलो "कनेक्ट" / "रचना" / "लिंक" फ़ंक्शन के लिए एक विशेष फ़ंक्शन है । इस तरह, हम पर्दे के पीछे, निम्नलिखित को खिलाने के लिए एक फ़ंक्शन के आउटपुट को अनुकूलित कर सकते हैं।

    हमारे मामले में: g >>= f(कनेक्ट / लिखें gकरने के लिए f)। हम आउटपुट >>=प्राप्त करना चाहते हैं g, इसका निरीक्षण करते हैं और अगर यह Nothingसिर्फ कॉल fऔर रिटर्न नहीं है Nothing; या इसके विपरीत, बॉक्सिंग निकालें और इसके साथ Realफ़ीड fकरें। (यह एल्गोरिदम केवल प्रकार के >>=लिए कार्यान्वयन है Maybe)। यह भी ध्यान दें कि "बॉक्सिंग प्रकार" (अलग-अलग बॉक्स, अलग-अलग अडॉप्टिंग एल्गोरिदम) केवल एक बार>>= लिखा जाना चाहिए ।

  6. कई अन्य समस्याएं उत्पन्न होती हैं जिन्हें इसी पैटर्न का उपयोग करके हल किया जा सकता है: 1. विभिन्न अर्थों / मूल्यों को संहिताबद्ध / संग्रहीत करने के लिए "बॉक्स" का उपयोग करें, और ऐसे कार्य होते हैं gजो उन "बॉक्सिंग मूल्यों" को वापस करते हैं। 2. एक संगीतकार / लिंकर के आउटपुट को 's इनपुट से g >>= fजोड़ने में मदद करने के लिए है , इसलिए हमें कोई भी बदलाव नहीं करना है ।gff

  7. इस तकनीक का उपयोग करके हल की जा सकने वाली समस्याएँ हैं:

    • एक वैश्विक स्थिति है कि कार्यों के क्रम में हर कार्य ("कार्यक्रम") साझा कर सकते हैं: समाधान StateMonad

    • हम "अशुद्ध कार्यों" को पसंद नहीं करते हैं: ऐसे कार्य जो एक ही इनपुट के लिए अलग-अलग आउटपुट देते हैं । इसलिए, उन कार्यों को चिह्नित करते हैं, जो उन्हें टैग किए गए / बॉक्स किए गए मान को वापस करने के लिए बनाते हैं: मोनड।IO

कुल सुख!


64
@ कार्ल कृपया हमें सूचित करने के लिए एक बेहतर उत्तर लिखें
XrXr

15
@ कार्ल मुझे लगता है कि यह जवाब में स्पष्ट है कि इस पैटर्न (बिंदु 6) से लाभ उठाने वाली कई समस्याएं हैं और यह कि IOमोनाड सूची में केवल एक और समस्या है IO(बिंदु 7)। दूसरी ओर IOकेवल एक बार और अंत में प्रकट होता है, इसलिए, अपने "अधिकांश समय बात करते हुए ... आईओ के बारे में" न समझें।
cibercitizen1

4
भिक्षुओं के बारे में महान गलतफहमी: राज्य के बारे में भिक्षु; अपवाद से निपटने के बारे में सनक; भिक्षुओं के बिना शुद्ध एफपीएल में आईओ को लागू करने का कोई तरीका नहीं है; भिक्षु असंदिग्ध हैं (विरोधाभास है Either)। सबसे अधिक जवाब "हम क्यों फंक्शनलर्स की आवश्यकता है?" के बारे में है।
वलसाचू

4
6. "2. एक संगीतकार / लिंकर g >>= fको gआउटपुट के fइनपुट से जोड़ने में मदद करने के लिए है , इसलिए हमें कोई भी बदलाव नहीं करना है f।" यह बिल्कुल भी सही नहीं है । इससे पहले, में f(g(x,y)), fकुछ भी उत्पादन कर सकता था। यह हो सकता है f:: Real -> String। "मोनैडिक रचना" के साथ इसे उत्पादन के लिए बदलना होगाMaybe String , अन्यथा प्रकार फिट नहीं होंगे। इसके अलावा, >>=खुद को फिट नहीं है !! यह है >=>कि यह रचना करता है, नहीं >>=। कार्ल के जवाब के तहत डिफुअर के साथ चर्चा देखें।
विल नेस

3
आपका उत्तर इस अर्थ में सही है कि मोनड्स IMO को वास्तव में "फ़ंक्शन" (क्लीसली तीर वास्तव में) की रचना / समानता के बारे में सबसे अच्छा वर्णन किया गया है, लेकिन इस बात का सटीक विवरण है कि वे किस प्रकार से जाते हैं, जो उन्हें "सनक" बनाता है। आप सभी प्रकार के शिष्टाचारों (जैसे फ़न्क्टर, आदि) में बक्से को तार कर सकते हैं। उन्हें एक साथ तार करने का यह विशिष्ट तरीका "मोनाड" को परिभाषित करता है।
विल नेस

219

जवाब, ज़ाहिर है, "हम नहीं" । सभी सार के साथ के रूप में, यह आवश्यक नहीं है।

हास्केल को मोनाड अमूर्तता की आवश्यकता नहीं है। शुद्ध भाषा में IO करने के लिए यह आवश्यक नहीं है। IOप्रकार अपने आप में है कि ठीक का ख्याल रखता है। की मौजूदा monadic desugaring doब्लॉक करने के लिए desugaring साथ प्रतिस्थापित किया जा सकता है bindIO, returnIOऔर failIOके रूप में के रूप में परिभाषित GHC.Baseमॉड्यूल। (यह हैकेज पर एक प्रलेखित मॉड्यूल नहीं है, इसलिए मुझे इसके स्रोत को प्रलेखन के लिए इंगित करना होगा ।) तो नहीं, मोनाड अमूर्त की कोई आवश्यकता नहीं है।

तो अगर इसकी आवश्यकता नहीं है, तो इसका अस्तित्व क्यों है? क्योंकि यह पाया गया था कि कम्प्यूटेशन के कई पैटर्न मोनैडिक संरचनाओं का निर्माण करते हैं। एक संरचना का अमूर्त लेखन कोड के लिए अनुमति देता है जो उस संरचना के सभी उदाहरणों में काम करता है। इसे और अधिक संक्षेप में रखने के लिए - कोड का पुन: उपयोग।

कार्यात्मक भाषाओं में, कोड के पुन: उपयोग के लिए पाया जाने वाला सबसे शक्तिशाली उपकरण कार्यों की संरचना है। अच्छा पुराना (.) :: (b -> c) -> (a -> b) -> (a -> c)ऑपरेटर अत्यधिक शक्तिशाली है। यह छोटे कार्यों को लिखना आसान बनाता है और उन्हें न्यूनतम सिनेटिक या सिमेंटिक ओवरहेड के साथ गोंद करता है।

लेकिन ऐसे मामले हैं जब प्रकार बहुत सही काम नहीं करते हैं। जब आपके पास है foo :: (b -> Maybe c)और आप क्या करते हैं bar :: (a -> Maybe b)? foo . barटाइपकास्ट नहीं करता है, क्योंकि bऔर Maybe bएक ही प्रकार नहीं हैं।

लेकिन ... यह लगभग सही है। आप बस थोड़ा सा रास्ता चाहते हैं। आप Maybe bमूल रूप से इलाज करने में सक्षम होना चाहते हैं b। यह केवल फ्लैट-आउट के लिए एक खराब विचार है क्योंकि वे एक ही प्रकार के रूप में मानते हैं। कमोबेश यही बात अशक्त बिंदुओं के रूप में है, जिसे टोनी होरे ने अरब-डॉलर की गलती कहा । इसलिए यदि आप उन्हें एक ही प्रकार के रूप में नहीं मान सकते हैं, तो शायद आप रचना तंत्र (.)प्रदान करने का एक तरीका खोज सकते हैं ।

उस मामले में, वास्तव में अंतर्निहित सिद्धांत की जांच करना महत्वपूर्ण है (.)। सौभाग्य से, किसी ने पहले ही हमारे लिए यह कर दिया है। यह पता चला है कि एक श्रेणी के रूप में जाना जाने वाला गणितीय निर्माण (.)और संयोजन होता है । लेकिन श्रेणियां बनाने के अन्य तरीके हैं। उदाहरण के लिए, क्लेसीली श्रेणी, वस्तुओं को थोड़ा संवर्धित होने के लिए अनुमति देता है। के लिए एक क्लेइसली श्रेणी शामिल होगी और । यही है, श्रेणी में ऑब्जेक्ट एक के साथ संवर्धित होता है , इसलिए बन जाता है ।idMaybe(.) :: (b -> Maybe c) -> (a -> Maybe b) -> (a -> Maybe c)id :: a -> Maybe a(->)Maybe(a -> b)(a -> Maybe b)

और अचानक, हमने उन चीजों की संरचना की शक्ति को बढ़ाया है जो पारंपरिक (.)ऑपरेशन पर काम नहीं करते हैं। यह नई अमूर्त शक्ति का एक स्रोत है। क्लेली कैटिगरी सिर्फ और सिर्फ टाइप से काम करती है Maybe। वे हर प्रकार के साथ काम करते हैं जो एक उचित श्रेणी को इकट्ठा कर सकते हैं, श्रेणी कानूनों का पालन कर सकते हैं।

  1. वाम पहचान: id . f=f
  2. सही पहचान: f . id=f
  3. संघात: f . (g . h)=(f . g) . h

जब तक आप यह साबित कर सकते हैं कि आपका प्रकार उन तीन कानूनों का पालन करता है, तब तक आप इसे क्लेस्ली श्रेणी में बदल सकते हैं। और उसके बारे में क्या बड़ी बात है? ठीक है, यह पता चलता है कि क्लेडली श्रेणियों के रूप में मोनाड बिल्कुल वैसा ही है। Monadके returnKleisli रूप में ही है idMonadके (>>=)Kleisli के समान नहीं है (.), लेकिन यह पता चला अन्य के मामले में बहुत आसान प्रत्येक लिखने के लिए किया जाना है। और श्रेणी कानून मोनाड कानूनों के समान हैं, जब आप उन्हें (>>=)और के बीच के अंतर में अनुवाद करते हैं (.)

तो इन सब परेशानियों से क्यों गुजरें? Monadभाषा में अमूर्तता क्यों है ? जैसा कि मैंने ऊपर बताया था, यह कोड का पुन: उपयोग करने में सक्षम बनाता है। यह दो अलग-अलग आयामों के साथ कोड के पुन: उपयोग को भी सक्षम बनाता है।

कोड पुन: उपयोग का पहला आयाम सीधे अमूर्त की उपस्थिति से आता है। आप कोड लिख सकते हैं जो अमूर्तता के सभी उदाहरणों पर काम करता है। पूरे मोनड-लूप्स पैकेज हैं जिसमें लूप शामिल हैं जो किसी भी उदाहरण के साथ काम करते हैं Monad

दूसरा आयाम अप्रत्यक्ष है, लेकिन यह रचना के अस्तित्व से आता है। जब रचना आसान होती है, तो छोटे, पुन: प्रयोज्य विखंडू में कोड लिखना स्वाभाविक है। यह उसी तरह है जैसे (.)कार्यों के लिए ऑपरेटर छोटे, पुन: प्रयोज्य कार्यों को लिखने के लिए प्रोत्साहित करता है।

तो अमूर्तन क्यों मौजूद है? क्योंकि यह एक उपकरण साबित होता है जो कोड में अधिक संरचना को सक्षम करता है, जिसके परिणामस्वरूप पुन: प्रयोज्य कोड बनाया जाता है और अधिक पुन: प्रयोज्य कोड के निर्माण को प्रोत्साहित किया जाता है। कोड का पुन: उपयोग प्रोग्रामिंग की पवित्र कब्रों में से एक है। मोनाड अमूर्त अस्तित्व में है क्योंकि यह हमें उस पवित्र कब्र की ओर थोड़ा सा स्थानांतरित करता है।


2
क्या आप श्रेणियों और आम तौर पर क्लेस्ली श्रेणियों के बीच संबंधों की व्याख्या कर सकते हैं? आपके द्वारा वर्णित तीन कानून किसी भी श्रेणी में हैं।
20

1
@dfeuer ओह। , कोड में कहें newtype Kleisli m a b = Kleisli (a -> m b)। क्लेइसी श्रेणियां वे कार्य हैं जहां श्रेणीगत वापसी प्रकार ( bइस मामले में) एक प्रकार के निर्माता के लिए तर्क है m। इफ Kleisli mएक श्रेणी बनाता है, mएक मोनाड है।
कार्ल

1
वास्तव में एक श्रेणीगत रिटर्न प्रकार क्या है? Kleisli mएक वर्ग जिसका ऑब्जेक्ट से तीर हास्केल प्रकार और इस तरह कर रहे हैं बनाने के लिए लगता है aके लिए bसे कार्य हैं aकरने के लिए m bके साथ, id = returnऔर (.) = (<=<)। क्या यह सही है, या मैं चीजों के विभिन्न स्तरों या कुछ को मिला रहा हूं?
दोपहर

1
@ पीडीएफर यह सही है। वस्तुएं सभी प्रकार की हैं, और आकारिकी प्रकारों के बीच हैं aऔर b, लेकिन वे सरल कार्य नहीं हैं। वे mफ़ंक्शन के रिटर्न मान में एक अतिरिक्त के साथ सजाए गए हैं ।
कार्ल

1
क्या श्रेणी सिद्धांत शब्दावली की वास्तव में आवश्यकता है? हो सकता है, हास्केल आसान होगा यदि आप चित्रों को प्रकारों में बदल देते हैं जहां प्रकार डीएनए होगा कि चित्र कैसे खींचे जाते हैं (निर्भर प्रकार हालांकि *), और फिर आप अपने कार्यक्रम को छोटे रूबी वर्णों के नाम के साथ लिखने के लिए चित्र का उपयोग करते हैं आइकन के ऊपर।
aoeu256

24

बेंजामिन पियर्स ने TAPL में कहा

एक प्रकार की प्रणाली को एक कार्यक्रम में शर्तों के रन-टाइम व्यवहारों के लिए एक प्रकार के स्थैतिक सन्निकटन की गणना के रूप में माना जा सकता है।

इसीलिए एक खराब टाइप की भाषा की तुलना में एक शक्तिशाली प्रकार की प्रणाली से लैस भाषा कड़ाई से अधिक अभिव्यंजक है। आप उसी तरह से भिक्षुओं के बारे में सोच सकते हैं।

@Carl और sigfpe बिंदु के रूप में, आप उन सभी प्रचालनों के साथ डेटाटाइप को लैस कर सकते हैं, जिन्हें आप मोनाड्स, टाइपकालेज़ या जो भी अन्य सार सामान का सहारा लिए बिना चाहते हैं। हालांकि मोनैडस आपको न केवल पुन: प्रयोज्य कोड लिखने की अनुमति देता है, बल्कि सभी निरर्थक विवरणों का सार भी देता है।

एक उदाहरण के रूप में, मान लें कि हम किसी सूची को फ़िल्टर करना चाहते हैं। filterफ़ंक्शन का उपयोग करने का सबसे सरल तरीका है :, filter (> 3) [1..10]जो बराबर है [4,5,6,7,8,9,10]

का थोड़ा और अधिक जटिल संस्करण filter, जो बाएं से दाएं एक संचायक भी गुजरता है, है

swap (x, y) = (y, x)
(.*) = (.) . (.)

filterAccum :: (a -> b -> (Bool, a)) -> a -> [b] -> [b]
filterAccum f a xs = [x | (x, True) <- zip xs $ snd $ mapAccumL (swap .* f) a xs]

सभी प्राप्त करने के लिए i, कि इस तरह के i <= 10, sum [1..i] > 4, sum [1..i] < 25, हम लिख सकते

filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]

जो बराबर है [3,4,5,6]

या हम उस nubफ़ंक्शन को पुनर्परिभाषित कर सकते हैं , जो सूची से डुप्लिकेट तत्वों को हटाता है filterAccum: के संदर्भ में

nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []

nub' [1,2,4,5,4,3,1,8,9,4]बराबर होता है [1,2,4,5,3,8,9]। एक सूची यहां एक संचायक के रूप में पारित की जाती है। कोड काम करता है, क्योंकि सूची में मोनड को छोड़ना संभव है, इसलिए पूरी गणना शुद्ध रहती है ( वास्तव में notElemउपयोग नहीं करता है >>=, लेकिन यह हो सकता है)। हालाँकि, IO मोनड को सुरक्षित रूप से छोड़ना संभव नहीं है (अर्थात आप IO क्रिया को निष्पादित नहीं कर सकते हैं और शुद्ध मान लौटा सकते हैं - मान हमेशा IO मुद्रा में लिपटा रहेगा)। एक अन्य उदाहरण परस्पर सारणी है: जब आप ST मोनाड को लीकेज कर लेते हैं, जहां एक उत्परिवर्तित सरणी रहते हैं, आप सरणी को निरंतर समय में अपडेट नहीं कर सकते। इसलिए हमें Control.Monadमॉड्यूल से एक मानद फ़िल्टरिंग की आवश्यकता है :

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do
   flg <- p x
   ys  <- filterM p xs
   return (if flg then x:ys else ys)

filterMएक सूची से सभी तत्वों के लिए एक राक्षसी कार्रवाई को निष्पादित करता है, तत्वों की पैदावार करता है, जिसके लिए राक्षसी कार्रवाई वापस आती है True

एक सरणी के साथ एक फ़िल्टरिंग उदाहरण:

nub' xs = runST $ do
        arr <- newArray (1, 9) True :: ST s (STUArray s Int Bool)
        let p i = readArray arr i <* writeArray arr i False
        filterM p xs

main = print $ nub' [1,2,4,5,4,3,1,8,9,4]

[1,2,4,5,3,8,9]उम्मीद के मुताबिक प्रिंट ।

और IO मोनाद के साथ एक संस्करण, जो पूछता है कि किन तत्वों को वापस लौटना है:

main = filterM p [1,2,4,5] >>= print where
    p i = putStrLn ("return " ++ show i ++ "?") *> readLn

उदाहरण के लिए

return 1? -- output
True      -- input
return 2?
False
return 4?
False
return 5?
True
[1,5]     -- output

और अंतिम दृष्टांत के filterAccumरूप में, के संदर्भ में परिभाषित किया जा सकता है filterM:

filterAccum f a xs = evalState (filterM (state . flip f) xs) a

StateTमठ के साथ , कि हुड के तहत प्रयोग किया जाता है, सिर्फ एक साधारण डेटाटाइप होने के नाते।

यह उदाहरण दिखाता है, कि मोनड्स न केवल आपको सारगर्भित संदर्भ देने की अनुमति देते हैं और स्वच्छ पुन: प्रयोज्य कोड लिखते हैं (क्योंकि मोनडर्स की रचनाशीलता के कारण @Carl बताते हैं), लेकिन यह भी उपयोगकर्ता-परिभाषित डेटाटाइप्स और अंतर्निहित प्राइमेटिव्स को समान रूप से व्यवहार करने के लिए।


1
यह उत्तर बताता है कि हमें मोनाड टाइपकास्ट की आवश्यकता क्यों है। समझने का सबसे अच्छा तरीका है, हमें मोनाड्स की आवश्यकता क्यों है और कुछ और नहीं, मोनाड्स और ऐप्लिकेटिव फ़ंक्शंस के बीच अंतर के बारे में पढ़ने के लिए है: एक , दो
user3237465

20

मुझे नहीं लगता कि IOइसे एक विशेष रूप से उत्कृष्ट मठ के रूप में देखा जाना चाहिए, लेकिन यह निश्चित रूप से शुरुआती लोगों के लिए अधिक आश्चर्यजनक है, इसलिए मैं इसे अपने स्पष्टीकरण के लिए उपयोग करूंगा।

हास्केल के लिए IO प्रणाली का निर्माण करना

विशुद्ध रूप से कार्यात्मक भाषा के लिए सबसे सरल बोधगम्य IO प्रणाली (और वास्तव में एक हास्केल के साथ शुरू हुआ) यह है:

main :: String -> String
main _ = "Hello World"

आलसीपन के साथ, यह सरल हस्ताक्षर वास्तव में इंटरैक्टिव टर्मिनल प्रोग्राम बनाने के लिए पर्याप्त है - हालांकि बहुत सीमित है। अधिकांश निराशा यह है कि हम केवल पाठ का आउटपुट कर सकते हैं। क्या होगा अगर हमने कुछ और रोमांचक आउटपुट संभावनाओं को जोड़ा?

data Output = TxtOutput String
            | Beep Frequency

main :: String -> [Output]
main _ = [ TxtOutput "Hello World"
          -- , Beep 440  -- for debugging
          ]

प्यारा, लेकिन निश्चित रूप से एक बहुत अधिक यथार्थवादी "परिवर्तनशील उत्पादन" एक फ़ाइल के लिए लिख रहा होगा । लेकिन फिर आप फ़ाइलों से पढ़ने का कोई तरीका भी चाहते हैं । कोई मौका?

ठीक है, जब हम अपना main₁कार्यक्रम लेते हैं और बस एक फाइल को प्रक्रिया में डालते हैं (ऑपरेटिंग सिस्टम सुविधाओं का उपयोग करके), हमने अनिवार्य रूप से फाइल-रीडिंग को लागू किया है। यदि हम हास्केल भाषा के भीतर से उस फ़ाइल-रीडिंग को ट्रिगर कर सकते हैं ...

readFile :: Filepath -> (String -> [Output]) -> [Output]

यह एक "इंटरएक्टिव प्रोग्राम" का उपयोग करता है String->[Output], इसे एक फ़ाइल से प्राप्त स्ट्रिंग खिलाता है, और एक गैर-इंटरैक्टिव प्रोग्राम प्राप्त करता है जो बस दिए गए को निष्पादित करता है।

यहाँ एक समस्या है: जब फ़ाइल को पढ़ा जाता है तो हमारे पास वास्तव में कोई धारणा नहीं होती है। [Output]सूची यकीन है के लिए एक अच्छा आदेश देता है आउटपुट , लेकिन हम जब के लिए एक आदेश नहीं मिलता आदानों किया जाएगा।

समाधान: इनपुट-ईवेंट को करने के लिए चीजों की सूची में आइटम भी बनाते हैं।

data IO = TxtOut String
         | TxtIn (String -> [Output])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [Output])
         | Beep Double

main :: String -> [IO₀]
main _ = [ FileRead "/dev/null" $ \_ ->
             [TxtOutput "Hello World"]
          ]

ठीक है, अब आप एक असंतुलन को देख सकते हैं: आप एक फ़ाइल पढ़ सकते हैं और उस पर निर्भर उत्पादन कर सकते हैं, लेकिन आप किसी अन्य फ़ाइल को पढ़ने के लिए फ़ाइल सामग्री का उपयोग करने का निर्णय नहीं ले सकते। स्पष्ट समाधान: इनपुट-घटनाओं के परिणाम को भी कुछ लिखें IO, न कि केवल Output। यह सुनिश्चित करता है कि सरल पाठ आउटपुट शामिल है, लेकिन अतिरिक्त फ़ाइलों आदि को पढ़ने की भी अनुमति देता है।

data IO = TxtOut String
         | TxtIn (String -> [IO₁])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [IO₁])
         | Beep Double

main :: String -> [IO₁]
main _ = [ TxtIn $ \_ ->
             [TxtOut "Hello World"]
          ]

यह अब वास्तव में आपको किसी भी फ़ाइल ऑपरेशन को व्यक्त करने की अनुमति दे सकता है जिसे आप एक कार्यक्रम में (शायद अच्छे प्रदर्शन के साथ नहीं) चाहते हैं, लेकिन यह कुछ हद तक अधिक है:

  • main₃क्रियाओं की एक पूरी सूची देता है। हम केवल हस्ताक्षर का उपयोग क्यों नहीं करते हैं :: IO₁, जिसमें यह एक विशेष मामला है?

  • सूचियाँ वास्तव में अब कार्यक्रम प्रवाह का एक विश्वसनीय अवलोकन नहीं देती हैं: कुछ इनपुट ऑपरेशन के परिणामस्वरूप सबसे बाद की गणना केवल "घोषित" की जाएगी। इसलिए हम सूची संरचना को भी खोद सकते हैं, और बस प्रत्येक आउटपुट ऑपरेशन के लिए "और फिर" करें।

data IO = TxtOut String IO
         | TxtIn (String -> IO₂)
         | Terminate

main :: IO
main = TxtIn $ \_ ->
         TxtOut "Hello World"
          Terminate

इतना भी बेकार नहीं!

तो भिक्षुओं के साथ क्या करना है?

व्यवहार में, आप अपने सभी कार्यक्रमों को परिभाषित करने के लिए सादे निर्माणकर्ताओं का उपयोग नहीं करना चाहेंगे। इस तरह के मौलिक निर्माणकर्ताओं के लिए एक अच्छी जोड़ी होने की आवश्यकता होगी, फिर भी अधिकांश उच्च-स्तरीय सामान के लिए हम कुछ अच्छे उच्च-स्तरीय हस्ताक्षर के साथ एक फ़ंक्शन लिखना चाहेंगे। यह पता चला है कि इनमें से अधिकांश काफी समान दिखेंगे: किसी प्रकार के सार्थक-प्रकार के मूल्य को स्वीकार करें, और परिणाम के रूप में एक IO कार्रवाई करें।

getTime :: (UTCTime -> IO₂) -> IO
randomRIO :: Random r => (r,r) -> (r -> IO₂) -> IO
findFile :: RegEx -> (Maybe FilePath -> IO₂) -> IO

जाहिर है यहां एक पैटर्न है, और हम इसे बेहतर रूप में लिखेंगे

type IO a = (a -> IO₂) -> IO    -- If this reminds you of continuation-passing
                                  -- style, you're right.

getTime :: IO UTCTime
randomRIO :: Random r => (r,r) -> IO r
findFile :: RegEx -> IO (Maybe FilePath)

अब यह परिचित लगने लगता है, लेकिन हम अभी भी केवल हुड के तहत पतले-प्रच्छन्न सादे कार्यों से निपट रहे हैं, और यह जोखिम भरा है: प्रत्येक "मूल्य-कार्रवाई" में वास्तव में किसी भी निहित फ़ंक्शन के परिणामस्वरूप कार्रवाई करने की ज़िम्मेदारी है (अन्यथा पूरे कार्यक्रम के नियंत्रण प्रवाह को बीच में एक बीमार व्यवहार से आसानी से बाधित किया जाता है)। हम बेहतर ढंग से उस आवश्यकता को स्पष्ट करेंगे। ठीक है, यह पता चलता है कि वे मोनड कानून हैं , हालांकि मुझे यकीन नहीं है कि हम मानक बाँध / ऑपरेटरों के बिना वास्तव में उन्हें तैयार कर सकते हैं।

किसी भी दर पर, हम अब IO के एक सूत्रीकरण तक पहुँच गए हैं जिसमें एक उचित मौद्रिक उदाहरण है:

data IO a = TxtOut String (IO a)
           | TxtIn (String -> IO a)
           | TerminateWith a

txtOut :: String -> IO ()
txtOut s = TxtOut s $ TerminateWith ()

txtIn :: IO String
txtIn = TxtIn $ TerminateWith

instance Functor IO where
  fmap f (TerminateWith a) = TerminateWith $ f a
  fmap f (TxtIn g) = TxtIn $ fmap f . g
  fmap f (TxtOut s c) = TxtOut s $ fmap f c

instance Applicative IO where
  pure = TerminateWith
  (<*>) = ap

instance Monad IO where
  TerminateWith x >>= f = f x
  TxtOut s c >>= f = TxtOut s $ c >>= f
  TxtIn g >>= f = TxtIn $ (>>=f) . g

जाहिर है कि यह आईओ का कुशल कार्यान्वयन नहीं है, लेकिन यह सिद्धांत रूप में प्रयोग करने योग्य है।


@jdlugosz: IO3 a ≡ Cont IO2 a। लेकिन मेरा मतलब था कि उन लोगों के लिए एक टिप्पणी के रूप में जो पहले से ही निरंतरता को जानते हैं, क्योंकि यह बिल्कुल भी अनुकूल नहीं है।
लेफ्टरनैबाउट

4

आवर्ती समस्याओं के एक वर्ग को हल करने के लिए मोनाड सिर्फ एक सुविधाजनक ढांचा है। सबसे पहले, monads होना चाहिए functors (यानी तत्वों (या उनके प्रकार) की ओर देखे बिना मानचित्रण का समर्थन करना चाहिए), वे भी एक लाना होगा बंधन (या चेनिंग) संचालन और एक तत्व प्रकार से एक monadic मूल्य (बनाने के लिए एक तरह से return)। अंत में, bindऔर returnदो समीकरण (बाएं और दाएं पहचान) को संतुष्ट करना चाहिए, जिसे मोनाद कानून भी कहा जाता है। (वैकल्पिक रूप से कोई भी flattening operationबाध्यकारी के बजाय भिक्षुओं को परिभाषित कर सकता है ।)

सूची इकाई आमतौर पर गैर-नियतिवाद से निपटने के लिए प्रयोग किया जाता है। बाइंड ऑपरेशन सूची के एक तत्व का चयन करता है (सहज रूप से सभी समानांतर दुनिया में ), प्रोग्रामर को उनके साथ कुछ गणना करने की अनुमति देता है, और फिर सभी दुनिया के परिणामों को एकल सूची (समवर्ती, या समतल करके), एक नेस्टेड सूची में जोड़ता है। )। यहां बताया गया है कि कैसे एक हास्केल के मोनडिक ढांचे में क्रमपरिवर्तन कार्य को परिभाषित करेगा:

perm [e] = [[e]]
perm l = do (leader, index) <- zip l [0 :: Int ..]
            let shortened = take index l ++ drop (index + 1) l
            trailer <- perm shortened
            return (leader : trailer)

यहाँ एक उदाहरण है repl सत्र:

*Main> perm "a"
["a"]
*Main> perm "ab"
["ab","ba"]
*Main> perm ""
[]
*Main> perm "abc"
["abc","acb","bac","bca","cab","cba"]

यह ध्यान दिया जाना चाहिए कि सूची मोनाड किसी भी तरह से एक पक्ष प्रभावकारी संगणना नहीं है। एक गणितीय संरचना एक मोनाद (अर्थात उपर्युक्त इंटरफेस और कानूनों के अनुरूप) का साइड इफेक्ट नहीं होता है, हालांकि साइड-इफ़ेक्टिंग घटनाएं अक्सर अच्छी तरह से मोनैडिक फ्रेमवर्क में फिट होती हैं।


3

मोनाड मूल रूप से एक श्रृंखला में एक साथ कार्यों की रचना करने के लिए काम करते हैं। अवधि।

अब वे जिस तरह से रचना करते हैं, वह मौजूदा मठों में भिन्न होता है, जिसके परिणामस्वरूप विभिन्न व्यवहार होते हैं (जैसे, राज्य के मठ में उत्परिवर्तनीय स्थिति का अनुकरण करना)।

भिक्षुओं के बारे में भ्रम यह है कि इतने सामान्य होने के नाते, अर्थात, कार्यों की रचना करने के लिए एक तंत्र, उनका उपयोग कई चीजों के लिए किया जा सकता है, इस प्रकार लोगों का मानना ​​है कि भिक्षु राज्य के बारे में हैं, आईओ के बारे में, आदि, जब वे केवल "रचना कार्यों" के बारे में हैं। "।

अब, मोनड्स के बारे में एक दिलचस्प बात यह है कि रचना का परिणाम हमेशा "एम" प्रकार का होता है, अर्थात "एम" के साथ टैग किए गए एक लिफाफे के अंदर का मूल्य। इस सुविधा को लागू करने के लिए वास्तव में अच्छा लगता है, उदाहरण के लिए, अशुद्ध कोड से शुद्ध के बीच एक स्पष्ट अलगाव: प्रकार के कार्यों को "IO" के रूप में घोषित करें और IO सनक को परिभाषित करते हुए कोई फ़ंक्शन प्रदान न करें, "बाहर निकालने के लिए" "IO" के अंदर से एक "मान"। नतीजा यह है कि कोई भी फ़ंक्शन शुद्ध नहीं हो सकता है और एक ही समय में "IO" से एक मान निकाल सकते हैं, क्योंकि शुद्ध रहते हुए ऐसे मूल्य लेने का कोई तरीका नहीं है (फ़ंक्शन का उपयोग करने के लिए "IO" मोनड के अंदर होना चाहिए ऐसा मूल्य)। (ध्यान दें: ठीक है, कुछ भी सही नहीं है, इसलिए "असुरक्षित स्ट्रेटजैकेट" "असुरक्षितPerformIO: IO a -> a" का उपयोग करके तोड़ा जा सकता है


2

यदि आपके पास एक प्रकार का कंस्ट्रक्टर है और उस प्रकार के परिवार के मूल्यों को लौटाने वाले फ़ंक्शन हैं, तो आपको मोनाड्स की आवश्यकता है । आखिरकार, आप इस तरह के कार्यों को एक साथ जोड़ना चाहेंगे । ये तीन प्रमुख तत्व हैं क्यों जवाब देने के लिए ।

मुझे विस्तार से बताएं आपके पास Int, Stringऔर Realऔर प्रकार के कार्यों Int -> String, String -> Realऔर पर इतना। आप इन कार्यों को आसानी से जोड़ सकते हैं, जिनके साथ समाप्त हो रहा है Int -> Real। ज़िंदगी अच्छी है।

फिर, एक दिन, आपको नए प्रकार के परिवार बनाने की आवश्यकता है । ऐसा इसलिए हो सकता है क्योंकि आपको कोई मूल्य नहीं लौटाने की संभावना पर विचार करने की आवश्यकता है ( Maybe), एक त्रुटि ( Either), कई परिणाम ( List) और इतने पर वापसी ।

ध्यान दें कि Maybeएक प्रकार का कंस्ट्रक्टर है। यह एक प्रकार लेता है, जैसे Intऔर एक नया प्रकार लौटाता है Maybe Int। पहली बात याद रखना , कोई रचनाकार नहीं, कोई सन्यासी नहीं।

बेशक, आप अपने प्रकार निर्माता का उपयोग करना चाहते अपने कोड में, और जल्द ही आप की तरह काम करता है के साथ समाप्त Int -> Maybe Stringऔर String -> Maybe Float। अब, आप आसानी से अपने कार्यों को संयोजित नहीं कर सकते। अब जीवन अच्छा नहीं है।

और यहाँ जब भिक्षुओं बचाव के लिए आते हैं। वे आपको उस तरह के कार्यों को फिर से संयोजित करने की अनुमति देते हैं। बस आपको रचना बदलने की जरूरत है के लिए > ==


2
इसका प्रकार परिवारों से कोई लेना-देना नहीं है। आप वास्तव में क्या बात कर रहे हैं?
dfeuer
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.