हाय, हैब्र।
आज हम ऐसे FP पैटर्न को Has
-क्लास के रूप Has
मानेंगे। यह कई कारणों से एक दिलचस्प बात है: सबसे पहले, हम एक बार फिर सुनिश्चित करेंगे कि एफपी में पैटर्न हैं। दूसरे, यह पता चलता है कि इस पैटर्न के कार्यान्वयन को मशीन को सौंपा जा सकता है, जो कि टाइपकास्टेस (और हैकेज लाइब्रेरी) के साथ एक दिलचस्प चाल के रूप में निकला, जो एक बार फिर हास्केल 2010 और आईएमएचओ के बाहर टाइप सिस्टम एक्सटेंशन की व्यावहारिक उपयोगिता को प्रदर्शित करता है, जो इस पैटर्न से बहुत अधिक दिलचस्प है। तीसरा, बिल्लियों के लिए एक अवसर।

हालाँकि, शायद Has
वर्ग Has
है, इसके वर्णन के साथ शुरू करना सार्थक है, क्योंकि सभी में कोई कमी नहीं है (और, विशेष रूप से, रूसी भाषी) विवरण।
तो, हास्केल कुछ वैश्विक रीड-ओनली वातावरण के प्रबंधन की समस्या को कैसे हल करता है, जिसमें कई विभिन्न कार्यों की आवश्यकता होती है? उदाहरण के लिए, आवेदन के वैश्विक विन्यास को कैसे व्यक्त किया जाता है?
सबसे स्पष्ट और प्रत्यक्ष समाधान यह है कि यदि किसी फ़ंक्शन को Env
प्रकार के मान की आवश्यकता है, तो आप इस फ़ंक्शन के लिए केवल Env
प्रकार का मान पास कर सकते हैं!
iNeedEnv :: Env -> Foo iNeedEnv env =
हालांकि, दुर्भाग्य से, इस तरह के एक समारोह बहुत ही रचना नहीं है, खासकर कुछ अन्य वस्तुओं की तुलना में जो हम हास्केल में उपयोग किए जाते हैं। उदाहरण के लिए, भिक्षुओं के साथ तुलना में।
वास्तव में, एक अधिक सामान्यीकृत समाधान उन कार्यों को लपेटने के लिए है जो Reader Env
मोनाड में Env
पर्यावरण तक पहुंच की आवश्यकता है:
import Control.Monad.Reader data Env = Env { someConfigVariable :: Int , otherConfigVariable :: [String] } iNeedEnv :: Reader Env Foo iNeedEnv = do
यह और भी अधिक सामान्यीकृत किया जा सकता है, जिसके लिए यह MonadReader MonadReader
का उपयोग करने के लिए पर्याप्त है और बस फ़ंक्शन के प्रकार को बदल दें:
iNeedEnv :: MonadReader Env m => m Foo iNeedEnv =
अब हमारे लिए यह बिल्कुल मायने नहीं रखता है कि हम किस मोनैडिक स्टैक में हैं, जब तक कि हम इससे Env
के प्रकार का मूल्य प्राप्त कर सकते हैं (और हम इसे स्पष्ट रूप से अपने फ़ंक्शन के प्रकार में व्यक्त करते हैं)। अगर पूरे स्टैक में IO
जैसी कोई अन्य सुविधाएँ हैं या MonadError
माध्यम से त्रुटि से निपटने में हमें कोई परवाह नहीं है:
someCaller :: (MonadIO m, MonadReader Env m, MonadError Err m) => m Bar someCaller = do theFoo <- iNeedEnv ...
और, वैसे, थोड़ा और अधिक, मैंने वास्तव में झूठ बोला था जब मैंने कहा था कि किसी फ़ंक्शन के लिए एक तर्क को स्पष्ट रूप से पारित करने का दृष्टिकोण मोनड्स के रूप में नहीं है: "आंशिक रूप से लागू" कार्यात्मक प्रकार r ->
एक सनक है, और, इसके अलावा, यह काफी है। MonadReader r
वर्ग का एक वैध उदाहरण। उपयुक्त अंतर्ज्ञान का विकास पाठक को एक अभ्यास के रूप में पेश किया जाता है।
किसी भी मामले में, यह प्रतिरूपकता की दिशा में एक अच्छा कदम है। आइए देखें कि वह हमें कहां ले जाता है।
क्यों है
आइए हम कुछ प्रकार की वेब सेवा पर काम करते हैं, जो अन्य बातों के अलावा, निम्नलिखित घटक हो सकते हैं:
- DB पहुँच परत
- वेब सर्वर
- टाइमर सक्रिय क्रोन-जैसे मॉड्यूल।
इनमें से प्रत्येक मॉड्यूल का अपना कॉन्फ़िगरेशन हो सकता है:
- डेटाबेस तक पहुंच का विवरण,
- वेब सर्वर के लिए होस्ट और पोर्ट,
- टाइमर ऑपरेशन अंतराल।
हम कह सकते हैं कि पूरे आवेदन का समग्र विन्यास इन सभी सेटिंग्स (और, शायद, कुछ और) का संयोजन है।
सादगी के लिए, मान लें कि प्रत्येक मॉड्यूल के एपीआई में केवल एक फ़ंक्शन होता है:
setupDatabase
startServer
runCronJobs
इन सुविधाओं में से प्रत्येक को एक उपयुक्त कॉन्फ़िगरेशन की आवश्यकता होती है। हमने पहले ही सीखा कि MonadReader
एक अच्छा अभ्यास है, लेकिन पर्यावरण का प्रकार क्या होगा?
सबसे स्पष्ट समाधान कुछ इस तरह होगा
data AppConfig = AppConfig { dbCredentials :: DbCredentials , serverAddress :: (Host, Port) , cronPeriodicity :: Ratio Int } setupDatabase :: MonadReader AppConfig m => m Db startServer :: MonadReader AppConfig m => m Server runCronJobs :: MonadReader AppConfig m => m ()
सबसे अधिक संभावना है, इन सुविधाओं के लिए MonadIO
और संभवतः, कुछ और की आवश्यकता होगी, लेकिन यह हमारी चर्चा के लिए इतना महत्वपूर्ण नहीं है।
वास्तव में, हमने सिर्फ एक भयानक काम किया। क्यों? खैर, ऑफहैंड:
- हमने पूरी तरह से अलग-अलग घटकों के बीच एक अनावश्यक कनेक्शन जोड़ा है। आदर्श रूप से, DB लेयर को किसी प्रकार के वेब सर्वर के बारे में कुछ भी पता नहीं होना चाहिए। और, ज़ाहिर है, हमें वेब सर्वर के लिए कॉन्फ़िगरेशन विकल्पों की सूची को बदलते समय डेटाबेस के साथ काम करने के लिए मॉड्यूल को फिर से नहीं करना चाहिए।
- यदि हम कुछ मॉड्यूल के लिए स्रोत कोड को संपादित नहीं कर सकते हैं तो यह बिल्कुल भी काम नहीं करेगा। उदाहरण के लिए, अगर किसी तीसरे पक्ष के पुस्तकालय में क्रोन मॉड्यूल लागू किया जाता है तो हमें क्या करना चाहिए जो हमारे विशिष्ट उपयोगकर्ता मामले के बारे में कुछ भी नहीं जानता है?
- हमने गलती करने के अवसर जोड़े। उदाहरण के लिए,
serverAddress
क्या है? क्या यह वह पता है जिसे वेब सर्वर को सुनना चाहिए, या डेटाबेस सर्वर का पता है? सभी विकल्पों के लिए एक बड़े प्रकार का उपयोग करने से इस तरह के टकराव की संभावना बढ़ जाती है। - हम अब फ़ंक्शन हस्ताक्षर पर एक नज़र से निष्कर्ष नहीं निकाल सकते हैं कि कौन सा मॉड्यूल कॉन्फ़िगरेशन के किस भाग का उपयोग करता है। हर चीज की पहुंच है सब कुछ!
तो इस सबका हल क्या है? जैसा कि आप लेख के शीर्षक से अनुमान लगा सकते हैं, यह
पैटर्न है
वास्तव में, प्रत्येक मॉड्यूल पूरे पर्यावरण के प्रकार के बारे में परवाह नहीं करता है, जब तक कि इस प्रकार के मॉड्यूल के लिए आवश्यक डेटा है। यह एक उदाहरण के साथ दिखाना सबसे आसान है।
डेटाबेस के साथ काम करने के लिए एक मॉड्यूल पर विचार करें और मान लें कि यह एक प्रकार को परिभाषित करता है जिसमें सभी कॉन्फ़िगरेशन की आवश्यकता होती है:
data DbConfig = DbConfig { dbCredentials :: DbCredentials , ... }
हैसपर्टन को निम्नलिखित टाइपसेकल्स के रूप में दर्शाया गया है:
class HasDbConfig rec where getDbConfig :: rec -> DbConfig
तब setupDatabase
टाइप दिखेगा
setupDatabase :: (MonadReader rm, HasDbConfig r) => m Db
और फ़ंक्शन के शरीर में हमें बस asks $ foo . getDbConfig
का उपयोग करना होगा asks $ foo . getDbConfig
asks $ foo . getDbConfig
जहाँ हम पहले इस्तेमाल करते थे, अतिरिक्त अमूर्त परत के कारण हमने पहले ही कहा था।
इसी तरह, हमारे पास HasWebServerConfig
और HasCronConfig
।
क्या होगा अगर कुछ फ़ंक्शन दो अलग-अलग मॉड्यूल का उपयोग करता है? बस संगत कसना!
doSmthWithDbAndCron :: (MonadReader rm, HasDbConfig r, HasCronConfig r) => ...
इन टाइपकास्ट के कार्यान्वयन के बारे में क्या?
हमारे पास अभी भी हमारे आवेदन के उच्चतम स्तर पर AppConfig
(बस अब मॉड्यूल को इसके बारे में पता नहीं है), और इसके लिए हम लिख सकते हैं:
data AppConfig = AppConfig { dbConfig :: DbConfig , webServerConfig :: WebServerConfig , cronConfig :: CronConfig } instance HasDbConfig AppConfig where getDbConfig = dbConfig instance HasWebServerConfig AppConfig where getWebServerConfig = webServerCOnfig instance HasCronConfig AppConfig where getCronConfig = cronConfig
यह अब तक अच्छा लग रहा है। हालांकि, इस दृष्टिकोण में एक समस्या है - बहुत अधिक लेखन , और हम अगले पोस्ट में अधिक विस्तार से इसकी जांच करेंगे।