शुद्ध, कार्यात्मक जावास्क्रिप्ट कोड में गंदे दुष्प्रभाव से निपटना

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

वैसे, कार्यात्मक प्रोग्रामर सही हैं। शुद्ध कार्य अच्छे हैं। लेकिन एक समस्या है ...


सामग्री के लेखक, जिसका अनुवाद हम आपके ध्यान में प्रस्तुत करते हैं, शुद्ध कार्यों में दुष्प्रभावों से कैसे निपटना है, इस बारे में बात करना चाहते हैं।

शुद्ध कार्यों की समस्या


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

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

  1. वे निर्भरता इंजेक्शन का लाभ उठाते हैं। मैं इसे बाड़ पर एक समस्या कह रहा हूं।
  2. वे फंक्शनलर्स का उपयोग करते हैं, जो मुझे शिथिलता का एक चरम रूप लगता है। यहां यह ध्यान दिया जाना चाहिए कि हास्केल में इसे "IO फंक्टर" या "IO मोनाड " कहा जाता है, PureScript में "इफेक्ट" शब्द का उपयोग किया जाता है, जो कि, मेरी राय में , फंक्शंस के सार का वर्णन करने के लिए थोड़ा बेहतर है।

निर्भरता इंजेक्शन


डिपेंडेंसी इंजेक्शन साइड इफेक्ट्स से निपटने का पहला तरीका है। इस दृष्टिकोण का उपयोग करते हुए, हम वह सब कुछ लेते हैं जो कोड को प्रदूषित करता है और इसे फ़ंक्शन के मापदंडों में डालता है। तब हम इस सब पर विचार कर सकते हैं जो किसी अन्य कार्य की जिम्मेदारी का हिस्सा है। मैं इसे निम्नलिखित उदाहरण के साथ समझाऊंगा:

// logSomething :: String -> String function logSomething(something) {    const dt = (new Date())toISOString();    console.log(`${dt}: ${something}`);    return something; } 

यहां मैं उन लोगों के लिए एक नोट बनाना चाहूंगा जो टाइप सिग्नेचर से परिचित हैं। यदि हम नियमों का कड़ाई से पालन करते हैं, तो हमें यहां दुष्प्रभावों को ध्यान में रखना होगा। लेकिन हम बाद में इससे निपटेंगे।

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

इस फ़ंक्शन को कैसे साफ करें? निर्भरता इंजेक्शन तकनीक का उपयोग करके, हम सब कुछ ले सकते हैं जो फ़ंक्शन को प्रदूषित करता है और इसे फ़ंक्शन पैरामीटर बनाता है। परिणामस्वरूप, एक पैरामीटर को स्वीकार करने के बजाय, हमारा कार्य तीन मापदंडों को स्वीकार करेगा:

 // logSomething: Date -> Console -> String -> * function logSomething(d, cnsl, something) {   const dt = d.toIsoString();   return cnsl.log(`${dt}: ${something}`); } 

अब, फ़ंक्शन को कॉल करने के लिए, हमें उस सब कुछ को स्थानांतरित करने की आवश्यकता है जो इसे पहले प्रदूषित करता है:

 const something = "Curiouser and curiouser!" const d = new Date(); logSomething(d, console, something); //  "Curiouser and curiouser!" 

यहाँ आप सोच सकते हैं कि यह सब बकवास है, कि हम केवल समस्या को एक स्तर ऊपर ले गए, और इससे हमारे कोड में शुद्धता नहीं आई। और आप जानते हैं, ये सही विचार हैं। यह अपने शुद्धतम रूप में एक खामी है।

यह एक अज्ञात अज्ञान की तरह है: “मुझे नहीं पता था कि cnsl ऑब्जेक्ट के log विधि को कॉल करने से I / O स्टेटमेंट का निष्पादन हो जाएगा। किसी ने इसे मुझे सौंप दिया, लेकिन मुझे नहीं पता कि यह सब कहां से आया है। " यह रवैया गलत है।

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

 const d = {toISOString: () => '1865-11-26T16:00:00.000Z'}; const cnsl = {   log: () => {       //      }, }; logSomething(d, cnsl, "Off with their heads!"); //   "Off with their heads!" 

अब हमारा कार्य कुछ भी नहीं करता है (यह केवल something पैरामीटर देता है)। लेकिन वह पूरी तरह से शुद्ध है। यदि आप इसे कई बार एक ही पैरामीटर के साथ कहते हैं, तो यह हर बार एक ही चीज़ लौटाएगा। और यह पूरी बात है। इस फ़ंक्शन को अशुद्ध बनाने के लिए, हमें जानबूझकर कुछ क्रियाएं करने की आवश्यकता है। या, इसे दूसरे तरीके से रखने के लिए, एक फ़ंक्शन पर निर्भर करता है कि सब कुछ अपने हस्ताक्षर में है। यह console या Date जैसी किसी भी वैश्विक ऑब्जेक्ट तक नहीं Date । यह सब कुछ औपचारिकता करता है।

इसके अलावा, यह ध्यान रखना महत्वपूर्ण है कि हम अपने फ़ंक्शन में अन्य फ़ंक्शन स्थानांतरित कर सकते हैं, जो पहले साफ नहीं था। एक और उदाहरण देखिए। कल्पना कीजिए कि किसी रूप में एक उपयोगकर्ता नाम है और हमें इस फॉर्म के संबंधित क्षेत्र का मूल्य प्राप्त करने की आवश्यकता है:

 // getUserNameFromDOM :: () -> String function getUserNameFromDOM() {   return document.querySelector('#username').value; } const username = getUserNameFromDOM(); username; //   "mhatter" 

इस मामले में, हम DOM से कुछ जानकारी लोड करने का प्रयास कर रहे हैं। शुद्ध कार्य ऐसा नहीं करते हैं, क्योंकि document एक वैश्विक वस्तु है जो किसी भी समय बदल सकती है। इस तरह के फ़ंक्शन को साफ करने का एक तरीका यह है कि इसे वैश्विक document ऑब्जेक्ट को एक पैरामीटर के रूप में पारित किया जाए। हालाँकि, आप अभी भी querySelector() फ़ंक्शन को पास कर सकते हैं। यह इस तरह दिखता है:

 // getUserNameFromDOM :: (String -> Element) -> String function getUserNameFromDOM($) {   return $('#username').value; } // qs :: String -> Element const qs = document.querySelector.bind(document); const username = getUserNameFromDOM(qs); username; //   "mhatter" 

यहाँ, फिर, आप इस विचार के साथ आ सकते हैं कि यह बेवकूफी है। आखिरकार, यहां हमने बस getUsernameFromDOM() फ़ंक्शन से हटा दिया जो हमें इसे साफ कॉल करने की अनुमति नहीं देता है। हालाँकि, हमें इससे छुटकारा नहीं मिला, बस कॉल को DOM से दूसरे फंक्शन, qs() ट्रांसफर कर दिया। यह लग सकता है कि इस कदम का एकमात्र ध्यान देने योग्य परिणाम यह था कि नया कोड पुराने की तुलना में लंबा था। एक अशुद्ध कार्य के बजाय, अब हमारे पास दो कार्य हैं, जिनमें से एक अभी भी अशुद्ध है।

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

 const qsStub = () => ({value: 'mhatter'}); const username = getUserNameFromDOM(qsStub); assert.strictEqual('mhatter', username, `Expected username to be ${username}`); 

यह, ज़ाहिर है, इसका मतलब यह नहीं है कि इस तरह के कार्यों का परीक्षण करने के लिए, एक वास्तविक ब्राउज़र में किए गए एकीकरण परीक्षण (या, कम से कम, जेएसडीओएम जैसी किसी चीज का उपयोग करके) की आवश्यकता नहीं है। लेकिन यह उदाहरण एक बहुत ही महत्वपूर्ण बात दर्शाता है, जो यह है कि अब getUserNameFromDOM() फ़ंक्शन पूरी तरह से अनुमानित हो गया है। यदि हम इसे करने के लिए qsStub() पास करते हैं, तो यह हमेशा वापस लौट mhatter । "अप्रत्याशितता" हम छोटे फ़ंक्शन qs() चले गए हैं।

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

Depend निर्भरता इंजेक्शन तंत्र के नुकसान


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

 function app(doc, con, ftch, store, config, ga, d, random) {   //     } app(document, console, fetch, store, config, ga, (new Date()), Math.random); 

वास्तव में, यह इतना बुरा नहीं है। इस तरह के निर्माण के नुकसान प्रकट होते हैं यदि कुछ मापदंडों को कुछ कार्यों को पारित करने की आवश्यकता होती है जो अन्य कार्यों में बहुत गहराई से एम्बेडेड होते हैं। ऐसा लगता है कि फ़ंक्शन कॉल के कई स्तरों के माध्यम से मापदंडों को पारित करने की आवश्यकता है। जब ऐसे स्तरों की संख्या बढ़ जाती है, तो यह गुस्सा करना शुरू कर देता है। उदाहरण के लिए, 5 मध्यवर्ती कार्यों के माध्यम से दिनांक का प्रतिनिधित्व करने वाली वस्तु को स्थानांतरित करना आवश्यक हो सकता है, जबकि कोई भी मध्यवर्ती कार्य इस वस्तु का उपयोग नहीं करता है। हालांकि, निश्चित रूप से, यह नहीं कहा जा सकता है कि ऐसी स्थिति सार्वभौमिक तबाही जैसी है। इसके अलावा, यह कार्यों की निर्भरता को स्पष्ट रूप से देखना संभव बनाता है। हालाँकि, ऐसा हो कि जैसा हो, यह अभी भी उतना सुखद नहीं है। इसलिए, हम निम्नलिखित तंत्र पर विचार करते हैं।

Functions आलसी कार्य


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

 // fZero :: () -> Number function fZero() {   console.log('Launching nuclear missiles');   //          return 0; } 

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

तो यहाँ एक अशुद्ध कार्य का एक उदाहरण है। यह कंसोल को डेटा आउटपुट देता है और परमाणु युद्ध का कारण भी है। हालाँकि, कल्पना कीजिए कि हमें उस शून्य की आवश्यकता है जो यह फ़ंक्शन देता है। एक परिदृश्य की कल्पना करें जिसमें हमें रॉकेट लॉन्च करने के बाद कुछ गणना करने की आवश्यकता है। कहो कि हमें एक उलटी गिनती टाइमर या ऐसा कुछ शुरू करने की आवश्यकता हो सकती है। इस मामले में, गणना करने के बारे में अग्रिम में सोचना पूरी तरह से स्वाभाविक होगा। और हमें यह सुनिश्चित करना चाहिए कि रॉकेट बिल्कुल जरूरत पड़ने पर लॉन्च हो। हमें इस तरह से गणना करने की आवश्यकता नहीं है कि वे गलती से इस रॉकेट के प्रक्षेपण के लिए नेतृत्व कर सकते हैं। तो आइए इस बारे में सोचें कि यदि हम किसी अन्य फ़ंक्शन में fZero() फ़ंक्शन को लपेटते हैं तो जो इसे वापस करता है। मान लीजिए कि यह सुरक्षा आवरण की तरह कुछ होगा:

 // fZero :: () -> Number function fZero() {   console.log('Launching nuclear missiles');   //          return 0; } // returnZeroFunc :: () -> (() -> Number) function returnZeroFunc() {   return fZero; } 

आप जितनी बार चाहें उतनी बार returnZeroFunc() फ़ंक्शन को कॉल कर सकते हैं। इस मामले में, जब तक यह वापस नहीं किया जाता है, तब तक इसे लागू करना, हम (सैद्धांतिक रूप से) सुरक्षित हैं। हमारे मामले में, इसका मतलब है कि निम्नलिखित कोड को निष्पादित करने से परमाणु युद्ध नहीं होगा:

 const zeroFunc1 = returnZeroFunc(); const zeroFunc2 = returnZeroFunc(); const zeroFunc3 = returnZeroFunc(); //     . 

अब पहले की तुलना में थोड़ा सख्त, आइए हम "शुद्ध कार्य" शब्द की परिभाषा को देखें। यह हमें अधिक विवरण में returnZeroFunc() फ़ंक्शन की जांच करने की अनुमति देगा। इसलिए, निम्न शर्तों के तहत फ़ंक्शन साफ ​​है:

  • कोई साइड इफेक्ट नहीं देखा।
  • लिंक पारदर्शिता। यही है, एक ही इनपुट मूल्यों के साथ इस तरह के एक फ़ंक्शन को कॉल करना हमेशा एक ही परिणाम की ओर जाता है।

returnZeroFunc() फ़ंक्शन returnZeroFunc() विश्लेषण करें।

क्या उसके कोई दुष्प्रभाव हैं? हमें अभी पता चला है कि कॉलिंग returnZeroFunc() मिसाइलों को लॉन्च नहीं करता है। यदि आप कॉल नहीं करते हैं कि यह फ़ंक्शन क्या देता है, तो कुछ भी नहीं होगा। इसलिए, हम यह निष्कर्ष निकाल सकते हैं कि इस फ़ंक्शन का कोई दुष्प्रभाव नहीं है।

क्या यह सुविधा संदर्भित रूप से पारदर्शी है? यही है, क्या यह हमेशा उसी इनपुट डेटा को पास करते समय वापस लौटता है? हम इसका सत्यापन करेंगे, इस तथ्य का लाभ उठाते हुए कि उपरोक्त कोड के टुकड़े में हमने इस फ़ंक्शन को कई बार कहा:

 zeroFunc1 === zeroFunc2; // true zeroFunc2 === zeroFunc3; // true 

यह सब अच्छा लग रहा है, लेकिन returnZeroFunc() फ़ंक्शन अभी तक पूरी तरह से साफ नहीं है। वह एक ऐसे चर को संदर्भित करती है जो उसके स्वयं के दायरे से बाहर है। इस समस्या को हल करने के लिए, हम फ़ंक्शन को फिर से लिखते हैं:

 // returnZeroFunc :: () -> (() -> Number) function returnZeroFunc() {   function fZero() {       console.log('Launching nuclear missiles');       //              return 0;   }   return fZero; } 

अब फ़ंक्शन को साफ माना जा सकता है। हालाँकि, इस स्थिति में, जावास्क्रिप्ट नियम हमारे खिलाफ खेलते हैं। अर्थात्, हम किसी फ़ंक्शन के संदर्भात्मक पारदर्शिता की जांच करने के लिए === ऑपरेटर का उपयोग नहीं कर सकते हैं। यह इस तथ्य के कारण है कि returnZeroFunc() हमेशा फ़ंक्शन का एक नया संदर्भ लौटाएगा। सही है, लिंक पारदर्शिता को स्वयं कोड की जांच करके सत्यापित किया जा सकता है। ऐसा विश्लेषण दिखाएगा कि प्रत्येक फ़ंक्शन कॉल के साथ यह उसी फ़ंक्शन का लिंक देता है।

हमसे पहले एक साफ सुथरा थोड़ा खामियाजा है। लेकिन क्या इसका इस्तेमाल वास्तविक परियोजनाओं में किया जा सकता है? इस प्रश्न का उत्तर सकारात्मक है। हालांकि, व्यवहार में इसका उपयोग कैसे करें, इसके बारे में बात करने से पहले, हम अपने विचार को थोड़ा विकसित करेंगे। अर्थात्, खतरनाक समारोह fZero() :

 // fZero :: () -> Number function fZero() {   console.log('Launching nuclear missiles');   //          return 0; } 

हम इस फ़ंक्शन द्वारा दिए गए शून्य का उपयोग करने का प्रयास करेंगे, लेकिन हम ऐसा करेंगे ताकि (अभी तक) एक परमाणु युद्ध शुरू न हो। ऐसा करने के लिए, एक फ़ंक्शन बनाएं जो fZero() फ़ंक्शन द्वारा लौटाए गए शून्य को लेता है और इसमें एक जोड़ता है:

 // fIncrement :: (() -> Number) -> Number function fIncrement(f) {   return f() + 1; } fIncrement(fZero); //      //   1 

यह दुर्भाग्य है ... हमने गलती से परमाणु युद्ध शुरू कर दिया। आइए फिर से कोशिश करें, लेकिन इस बार हमने एक नंबर वापस नहीं किया। इसके बजाय, हम एक फ़ंक्शन वापस करते हैं जो किसी दिन एक नंबर देता है:

 // fIncrement :: (() -> Number) -> (() -> Number) function fIncrement(f) {   return () => f() + 1; } fIncrement(zero); //   [Function] 

अब आप आसानी से सांस ले सकते हैं। तबाही टलती है। हम अध्ययन जारी रखते हैं। इन दो कार्यों के लिए धन्यवाद, हम "संभव संख्या" का एक पूरा गुच्छा बना सकते हैं:

 const fOne   = fIncrement(zero); const fTwo   = fIncrement(one); const fThree = fIncrement(two); //   … 

इसके अलावा, हम कई फ़ंक्शन बना सकते हैं जिनके नाम f से शुरू होंगे (चलो उन्हें f*() फ़ंक्शन कहते हैं), जिन्हें "संभव" के साथ काम करने के लिए डिज़ाइन किया गया है:

 // fMultiply :: (() -> Number) -> (() -> Number) -> (() -> Number) function fMultiply(a, b) {   return () => a() * b(); } // fPow :: (() -> Number) -> (() -> Number) -> (() -> Number) function fPow(a, b) {   return () => Math.pow(a(), b()); } // fSqrt :: (() -> Number) -> (() -> Number) function fSqrt(x) {   return () => Math.sqrt(x()); } const fFour = fPow(fTwo, fTwo); const fEight = fMultiply(fFour, fTwo); const fTwentySeven = fPow(fThree, fThree); const fNine = fSqrt(fTwentySeven); //    ,   . ! 

देखें कि हमने यहां क्या किया? "संभव संख्याओं" के साथ आप साधारण संख्याओं के साथ भी ऐसा ही कर सकते हैं। गणितज्ञ इस समरूपता को कहते हैं। एक साधारण संख्या को हमेशा एक फ़ंक्शन में रखकर "संभावित संख्या" में बदल दिया जा सकता है। आप फ़ंक्शन को कॉल करके "संभव संख्या" प्राप्त कर सकते हैं। दूसरे शब्दों में, हमारे पास नियमित संख्या और "संभव संख्या" के बीच मानचित्रण है। यह, वास्तव में, यह लग सकता है की तुलना में बहुत अधिक दिलचस्प है। जल्द ही हम इस विचार पर लौटेंगे।

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

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

फ़नकार प्रभाव


यहाँ हम हमारे "आस्थगित फ़ंक्शंस" वाले ऑब्जेक्ट द्वारा दर्शाए गए फ़ंक्शंस के बारे में बात करेंगे। फ़नकार का प्रतिनिधित्व करने के लिए, हम Effect ऑब्जेक्ट का उपयोग करेंगे। हम इस तरह के ऑब्जेक्ट में अपने फ़ंक्शन fZero() । लेकिन, ऐसा करने से पहले, हम इस समारोह को थोड़ा सुरक्षित करेंगे:

 // zero :: () -> Number function fZero() {   console.log('Starting with nothing');   //  , ,     .   //       .   return 0; } 

अब हम प्रकार के ऑब्जेक्ट बनाने के लिए कंस्ट्रक्टर फ़ंक्शन का वर्णन करते हैं:

 // Effect :: Function -> Effect function Effect(f) {   return {}; } 

यहाँ कुछ विशेष रूप से दिलचस्प नहीं है इसलिए हम इस सुविधा पर काम करेंगे। इसलिए, हम Effect वस्तु के साथ सामान्य fZero() फ़ंक्शन का उपयोग करना चाहते हैं। ऐसा परिदृश्य प्रदान करने के लिए, हम एक विधि लिखेंगे जो किसी नियमित कार्य को स्वीकार करती है और किसी दिन हमारे "लंबित मूल्य" पर लागू होती है। और हम इसे Effect फ़ंक्शन को कॉल किए बिना करेंगे। हम इस तरह के एक फ़ंक्शन map() । इस तथ्य के कारण इसका ऐसा नाम है कि यह सामान्य फ़ंक्शन और Effect फ़ंक्शन के बीच एक मैपिंग बनाता है। यह इस तरह लग सकता है:

 // Effect :: Function -> Effect function Effect(f) {   return {       map(g) {           return Effect(x => g(f(x)));       }   } } 

अब, यदि आप देखते हैं कि क्या हो रहा है, तो आप map() फ़ंक्शन के बारे में प्रश्न पूछ सकते हैं। यह गाने के समान ही संदिग्ध लगता है। हम बाद में इस मुद्दे पर वापस आएंगे, लेकिन अब हम परीक्षण करेंगे कि हमारे पास इस समय क्या है:

 const zero = Effect(fZero); const increment = x => x + 1; //   . const one = zero.map(increment); 

तो ... अब हमारे पास यह देखने का अवसर नहीं है कि यहाँ क्या हुआ। इसलिए, Effect को संशोधित करने के लिए, इसलिए बोलने के लिए, "ट्रिगर को खींचने" का अवसर प्राप्त करें:

 // Effect :: Function -> Effect function Effect(f) {   return {       map(g) {           return Effect(x => g(f(x)));       },       runEffects(x) {           return f(x);       }   } } const zero = Effect(fZero); const increment = x => x + 1; //  . const one = zero.map(increment); one.runEffects(); //       //   1 

यदि आवश्यक हो, तो हम map() फ़ंक्शन को कॉल करना जारी रख सकते हैं:

 const double = x => x * 2; const cube = x => Math.pow(x, 3); const eight = Effect(fZero)   .map(increment)   .map(double)   .map(cube); eight.runEffects(); //       //   8 

यहां, जो हो रहा है वह पहले से ही अधिक दिलचस्प होने लगा है। हम इसे "फ़नकार" कहते हैं। इसका मतलब यह है कि Effect वस्तु का एक map() फ़ंक्शन है और यह कुछ नियमों का पालन करता है । हालांकि, ये ऐसे नियम नहीं हैं जो किसी भी चीज को प्रतिबंधित करते हैं। ये नियम हैं कि आप क्या कर सकते हैं। वे विशेषाधिकारों की तरह हैं। चूंकि Effect ऑब्जेक्ट एक फ़नकार है, इसलिए यह इन नियमों का पालन करता है। विशेष रूप से, यह तथाकथित "रचना नियम" है।

यह इस तरह दिखता है:

यदि e , और दो फ़ंक्शन, f और g नामक कोई Effect वस्तु है, तो e.map(g).map(f) e.map(x => f(g(x))) बराबर है।

दूसरे शब्दों में, दो लगातार map() विधियां दो कार्यों की रचना के बराबर हैं। इसका मतलब यह है कि प्रकार की एक वस्तु निम्नलिखित के समान कार्य कर सकती है (उपरोक्त उदाहरणों में से एक को याद रखें):

 const incDoubleCube = x => cube(double(increment(x))); //       Ramda  lodash/fp      : // const incDoubleCube = compose(cube, double, increment); const eight = Effect(fZero).map(incDoubleCube); 

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

अब मैं संख्याओं के साथ प्रयोग करना बंद करने का प्रस्ताव करता हूं और इस बारे में बात करता हूं कि वास्तविक परियोजनाओं में उपयोग किए गए कोड की तरह क्या अधिक दिखता है।

▍Method of ()


Effect वस्तु का निर्माता एक तर्क, एक फ़ंक्शन के रूप में स्वीकार करता है। यह सुविधाजनक है, क्योंकि अधिकांश दुष्प्रभाव जो हम स्थगित करना चाहते हैं वे कार्य हैं। उदाहरण के लिए, ये Math.random() और console.log() । हालांकि, कभी-कभी आपको एक Effect ऑब्जेक्ट में एक मूल्य डालने की आवश्यकता होती है जो एक फ़ंक्शन नहीं है। , , window . , . , ( -, , , Haskell pure ):

 // of :: a -> Effect a Effect.of = function of(val) {   return Effect(() => val); } 

, , , -. , , . HTML- . , . . उदाहरण के लिए:

 window.myAppConf = {   selectors: {       'user-bio':     '.userbio',       'article-list': '#articles',       'user-name':    '.userfullname',   },   templates: {       'greet':  'Pleased to meet you, {name}',       'notify': 'You have {n} alerts',   } }; 

, Effect.of() , Effect :

 const win = Effect.of(window); userBioLocator = win.map(x => x.myAppConf.selectors['user-bio']); //   Effect('.userbio') 

▍ Effect


. , Effect . , getElementLocator() , Effect , . DOM, document.querySelector() — , . :

 // $ :: String -> Effect DOMElement function $(selector) {   return Effect.of(document.querySelector(s)); } 

, , map() :

 const userBio = userBioLocator.map($); //   Effect(Effect(<div>)) 

, , . div , map() , , . , innerHTML , :

 const innerHTML = userBio.map(eff => eff.map(domEl => domEl.innerHTML)); //   Effect(Effect('<h2>User Biography</h2>')) 

, . userBio , . , , , . , , Effect('user-bio') . , , , :

 Effect(() => '.userbio'); 

— . :

 Effect(() => window.myAppConf.selectors['user-bio']); 

, map() , ( ). , , $ , :

 Effect(() => $(window.myAppConf.selectors['user-bio'])); 

, :

 Effect(   () => Effect.of(document.querySelector(window.myAppConf.selectors['user-bio']))) ); 

Effect.of , :

 Effect(   () => Effect(       () => document.querySelector(window.myAppConf.selectors['user-bio'])   ) ); 

, , , . Effect .

▍ join()


? , Effect . , , .

Effect .runEffect() . . , - , , , , . , . join() . Effect , runEffect() , . , .

 // Effect :: Function -> Effect function Effect(f) {   return {       map(g) {           return Effect(x => g(f(x)));       },       runEffects(x) {           return f(x);       }       join(x) {           return f(x);       }   } } 

, :

 const userBioHTML = Effect.of(window)   .map(x => x.myAppConf.selectors['user-bio'])   .map($)   .join()   .map(x => x.innerHTML); //   Effect('<h2>User Biography</h2>') 

▍ chain()


, .map() , .join() , . , , . , , Effect . , .map() .join() . , , Effect :

 // Effect :: Function -> Effect function Effect(f) {   return {       map(g) {           return Effect(x => g(f(x)));       },       runEffects(x) {           return f(x);       }       join(x) {           return f(x);       }       chain(g) {           return Effect(f).map(g).join();       }   } } 

chain() - , , Effect ( , ). HTML- :

 const userBioHTML = Effect.of(window)   .map(x => x.myAppConf.selectors['user-bio'])   .chain($)   .map(x => x.innerHTML); //   Effect('<h2>User Biography</h2>') 

-. . , flatMap . , , — , , join() . Haskell, , bind . , - , , chain , flatMap bind — .

▍ Effect


Effect , . . , DOM, , ? , , , . , . — .

 // tpl :: String -> Object -> String const tpl = curry(function tpl(pattern, data) {   return Object.keys(data).reduce(       (str, key) => str.replace(new RegExp(`{${key}}`, data[key]),       pattern   ); }); 

. :

 const win = Effect.of(window); const name = win.map(w => w.myAppConfig.selectors['user-name'])   .chain($)   .map(el => el.innerHTML)   .map(str => ({name: str}); //   Effect({name: 'Mr. Hatter'}); const pattern = win.map(w => w.myAppConfig.templates('greeting')); //   Effect('Pleased to meet you, {name}'); 

, . . ( name pattern ) Effect . tpl() , , Effect .
, map() Effect tpl() :

 pattern.map(tpl); //   Effect([Function]) 

, . map() :

 map :: Effect a ~> (a -> b) -> Effect b 

:

 tpl :: String -> Object -> String 

, map() pattern , ( , tpl() ) Effect .

 Effect (Object -> String) 

pattern Effect . . Effect , . ap() :

 // Effect :: Function -> Effect function Effect(f) {   return {       map(g) {           return Effect(x => g(f(x)));       },       runEffects(x) {           return f(x);       }       join(x) {           return f(x);       }       chain(g) {           return Effect(f).map(g).join();       }       ap(eff) {            //  -  ap,    ,   eff   (  ).           //    map  ,    eff       (  'g')           //   g,     f()           return eff.map(g => g(f()));       }   } } 

.ap() :

 const win = Effect.of(window); const name = win.map(w => w.myAppConfig.selectors['user-name'])   .chain($)   .map(el => el.innerHTML)   .map(str => ({name: str})); const pattern = win.map(w => w.myAppConfig.templates('greeting')); const greeting = name.ap(pattern.map(tpl)); //   Effect('Pleased to meet you, Mr Hatter') 

, … , , .ap() . , , map() , ap() . , , .

. , . , , , Effect , ap() . , :

 // liftA2 :: (a -> b -> c) -> (Applicative a -> Applicative b -> Applicative c) const liftA2 = curry(function liftA2(f, x, y) {   return y.ap(x.map(f));   //      :   // return x.map(f).chain(g => y.map(g)); }); 

liftA2() , , . liftA3() :

 // liftA3 :: (a -> b -> c -> d) -> (Applicative a -> Applicative b -> Applicative c -> Applicative d) const liftA3 = curry(function liftA3(f, a, b, c) {   return c.ap(b.ap(a.map(f))); }); 

, liftA2() liftA3() Effect . , , ap() .

liftA2() :

 const win = Effect.of(window); const user = win.map(w => w.myAppConfig.selectors['user-name'])   .chain($)   .map(el => el.innerHTML)   .map(str => ({name: str}); const pattern = win.map(w => w.myAppConfig.templates['greeting']); const greeting = liftA2(tpl)(pattern, user); //   Effect('Pleased to meet you, Mr Hatter') 

?


, , , . ? , Effect ap() . , ? ?

: « , , ».

:

  • — ?
  • , Effect , ?


— . , , , . const pattern = window.myAppConfig.templates['greeting']; , , , :

 const pattern = Effect.of(window).map(w => w.myAppConfig.templates('greeting')); 

— , , , , . . — , , . , , , , , , . , . — . , , , . , .

. .

▍ Effect


, , . - Facebook Gmail . ? .

, . . CSV- . . , , , . , . , . , , , .

, . , map() reduce() , . . , . , , , . 4 (, , 8, 16, ). , , . , . , - .

, , . , . क्या कुछ भी समान नहीं है? , , , . . , .

TensorFlow , .

TensorFlow, , . «». , , :

 node1 = tf.constant(3.0, tf.float32) node2 = tf.constant(4.0, tf.float32) node3 = tf.add(node1, node2) 

Python, JavaScript. , Effect , add() , ( sess.run() ).

 print("node3: ", node3) print("sess.run(node3): ", sess.run(node3)) #  node3:  Tensor("Add_2:0", shape=(), dtype=float32) #  sess.run(node3):  7.0 

, (7.0) , sess.run() . , . , , , .

परिणाम


, . , . Effect .
, , , , , . , , . Effect , , , . , .

— . , . , , . . . , .



प्रिय पाठकों! ?

Source: https://habr.com/ru/post/hi422589/


All Articles