मैं _read_ कार्यात्मक जावास्क्रिप्ट कोड कैसे बना सकता हूं?


9

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

नीचे दिए गए कोड को लें। मैंने यह कोड लिखा था। इसका उद्देश्य दो वस्तुओं के बीच, कहने {a:1, b:2, c:3, d:3}और के बीच एक प्रतिशत समानता को निर्दिष्ट करना है {a:1, b:1, e:2, f:2, g:3, h:5}। मैंने स्टैक ओवरफ्लो पर इस सवाल के जवाब में कोड का उत्पादन किया । क्योंकि मुझे यकीन नहीं था कि पोस्टर किस तरह की प्रतिशतता के बारे में पूछ रहा था, मैंने चार अलग-अलग प्रकार प्रदान किए:

  • पहली वस्तु में प्रतिशत का प्रतिशत जो 2 में पाया जा सकता है,
  • 1 वस्तु में प्रतिशत का मान जो डुप्लिकेट सहित 2 में पाया जा सकता है,
  • 1 ऑब्जेक्ट में मानों का प्रतिशत 2 में पाया जा सकता है, जिसकी कोई डुप्लिकेट अनुमति नहीं है, और
  • 1 ऑब्जेक्ट में {key: value} जोड़े का प्रतिशत जो 2 ऑब्जेक्ट में पाया जा सकता है।

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

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

तो, क्या कोई मुझे बता सकता है कि कोड के कुछ और अधिक जटिल बिट्स को कैसे "पढ़ना" है जो संक्षिप्त है और जो मैं पढ़ रहा हूं उसकी मेरी समझ में योगदान देता है? मैं उन हिस्सों का अनुमान लगाता हूं जो मुझे सबसे अधिक मिलते हैं, वे हैं जो एक पंक्ति में कई वसा वाले तीर हैं और / या कुछ भागों में एक पंक्ति में कई कोष्ठक हैं। फिर, उनके मूल में, मैं अंततः तर्क का पता लगा सकता हूं, लेकिन (मुझे उम्मीद है) कार्यात्मक जावास्क्रिप्ट प्रोग्रामिंग की एक पंक्ति में जल्दी और स्पष्ट रूप से और सीधे "लेने" का एक बेहतर तरीका है।

नीचे से कोड की किसी भी लाइन का उपयोग करने के लिए स्वतंत्र महसूस करें, या अन्य उदाहरण भी। हालाँकि, यदि आप मुझसे कुछ प्रारंभिक सुझाव चाहते हैं, तो यहाँ कुछ हैं। यथोचित सरल से शुरुआत करें। कोड के अंत के पास से, यह एक फ़ंक्शन के पैरामीटर के रूप में पारित किया गया है obj => key => obj[key]:। कोई इसे कैसे पढ़ और समझ सकता है? एक लंबा उदाहरण शुरुआत के पास से एक पूर्ण कार्य है const getXs = (obj, getX) => Object.keys(obj).map(key => getX(obj)(key));:। अंतिम mapभाग मुझे विशेष रूप से मिलता है।

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

मुझे लगता है कि इस सवाल का एक हिस्सा यह है: क्या मैं कार्यात्मक कोड को रैखिक रूप से भी पढ़ सकता हूं, आप जानते हैं, बाएं से दाएं और ऊपर से नीचे? या कोड के पृष्ठ पर स्पेगेटी की तरह वायरिंग की मानसिक तस्वीर बनाने के लिए एक बहुत मजबूर है जो निश्चित रूप से रैखिक नहीं है ? और अगर किसी को ऐसा करना चाहिए , तो हमें अभी भी कोड पढ़ना है, तो हम रैखिक पाठ कैसे लेते हैं और स्पेगेटी को तार करते हैं?

किसी भी युक्तियां की सराहना की जाएगी।

const obj1 = { a:1, b:2, c:3, d:3 };
const obj2 = { a:1, b:1, e:2, f:2, g:3, h:5 };

// x or X is key or value or key/value pair

const getXs = (obj, getX) =>
  Object.keys(obj).map(key => getX(obj)(key));

const getPctSameXs = (getX, filter = vals => vals) =>
  (objA, objB) =>
    filter(getXs(objB, getX))
      .reduce(
        (numSame, x) =>
          getXs(objA, getX).indexOf(x) > -1 ? numSame + 1 : numSame,
        0
      ) / Object.keys(objA).length * 100;

const pctSameKeys       = getPctSameXs(obj => key => key);
const pctSameValsDups   = getPctSameXs(obj => key => obj[key]);
const pctSameValsNoDups = getPctSameXs(obj => key => obj[key], vals => [...new Set(vals)]);
const pctSameProps      = getPctSameXs(obj => key => JSON.stringify( {[key]: obj[key]} ));

console.log('obj1:', JSON.stringify(obj1));
console.log('obj2:', JSON.stringify(obj2));
console.log('% same keys:                   ', pctSameKeys      (obj1, obj2));
console.log('% same values, incl duplicates:', pctSameValsDups  (obj1, obj2));
console.log('% same values, no duplicates:  ', pctSameValsNoDups(obj1, obj2));
console.log('% same properties (k/v pairs): ', pctSameProps     (obj1, obj2));

// output:
// obj1: {"a":1,"b":2,"c":3,"d":3}
// obj2: {"a":1,"b":1,"e":2,"f":2,"g":3,"h":5}
// % same keys:                    50
// % same values, incl duplicates: 125
// % same values, no duplicates:   75
// % same properties (k/v pairs):  25

जवाबों:


18

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

function mapObj(obj, f) {
  return Object.keys(obj).map(key => f(obj, key));
}

function getPctSameXs(obj1, obj2, f) {
  const mapped1 = mapObj(obj1, f);
  const mapped2 = mapObj(obj2, f);
  const same = mapped1.filter(x => mapped2.indexOf(x) != -1);
  const percent = same.length / mapped1.length * 100;
  return percent;
}

const getValues = (obj, key) => obj[key];
const valuesWithDupsPercent = getPctSameXs(obj1, obj2, getValues);

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

यहां अभिव्यक्तियों को बहुत छोटे टुकड़ों में तोड़ दिया गया है और ऐसे नाम दिए गए हैं जो डोमेन के लिए सार्थक हैं। mapObjनामित फ़ंक्शन की तरह सामान्य कार्यक्षमता खींचकर नेस्टिंग से बचा जाता है। लेम्बडास संदर्भ में एक स्पष्ट उद्देश्य के साथ बहुत ही कम कार्यों के लिए आरक्षित हैं।

यदि आपको ऐसा कोड आता है जिसे पढ़ना मुश्किल है, तो इसे तब तक रिफ्लेक्टर करें जब तक यह आसान न हो। यह कुछ अभ्यास लेता है, लेकिन यह सार्थक है। कार्यात्मक कोड अनिवार्य के रूप में पठनीय हो सकता है। वास्तव में, अक्सर मोरेसो, क्योंकि यह आमतौर पर अधिक संक्षिप्त होता है।


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

1
मैंने सुना है कि लंबी श्रृंखलाएँ और / या तरीकों का घोंसला होना अनावश्यक मध्यवर्ती चरों को समाप्त करता है। इसके विपरीत, आपका जवाब अच्छी तरह से नामित मध्यवर्ती चर का उपयोग करके मध्यवर्ती स्टैंड-अलोन बयानों में मेरी जंजीरों / घोंसले को तोड़ता है। मुझे इस मामले में आपका कोड अधिक पठनीय लगता है, लेकिन मुझे आश्चर्य है कि आप कितने सामान्य होने की कोशिश कर रहे हैं। क्या आप कह रहे हैं कि लंबी विधि श्रृंखला और / या गहरी घोंसले अक्सर या यहां तक ​​कि हमेशा एक विरोधी पैटर्न से बचने के लिए होते हैं, या ऐसे समय होते हैं जब वे महत्वपूर्ण लाभ लाते हैं? और क्या कार्यात्मक बनाम अनिवार्य कोडिंग के लिए उस प्रश्न का उत्तर अलग है?
एंड्रयू विल्म्स

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

6

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

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

// getXs :: forall O, F . O -> (O -> String -> F) -> [F]
const getXs = (obj, getX) =>
    Object.keys(obj).map(key => getX(obj)(key));

इस तरह की परिभाषाओं के साथ काम करने में थोड़ा अभ्यास करने के साथ, वे एक फ़ंक्शन के अर्थ को बहुत स्पष्ट करते हैं।

नामकरण महत्वपूर्ण है, शायद प्रक्रियात्मक प्रोग्रामिंग की तुलना में भी अधिक। बहुत सारे कार्यात्मक कार्यक्रम बहुत ही आकर्षक शैली में लिखे गए हैं जो कि सम्मेलन पर भारी हैं (उदाहरण के लिए 'xs' एक सूची / सरणी है और यह 'x' एक आइटम है जो बहुत ही व्यापक है), लेकिन जब तक आप उस शैली को नहीं समझते हैं आसानी से मैं अधिक क्रिया नामकरण का सुझाव दूंगा। आपके द्वारा उपयोग किए जाने वाले विशिष्ट नामों को देखते हुए, "गेटएक्स" अपारदर्शी की तरह है, और इसलिए "गेटएक्स" वास्तव में या तो बहुत मदद नहीं करता है। मैं "getXs" को "applyToProperties" जैसा कुछ कहूंगा, और "getX" शायद "प्रॉपर्टीमैपर" होगा। "getPctSameXs" तब "centPropertiesSameWith" ("के साथ" होगा))।

एक और महत्वपूर्ण बात मुहावरेदार कोड लिखना है । मुझे लगता है कि आप एक वाक्यविन्यास का उपयोग कर रहे a => b => some-expression-involving-a-and-bहैं करी कार्यों का उत्पादन करने के लिए। यह दिलचस्प है, और कुछ स्थितियों में उपयोगी हो सकता है, लेकिन आप यहां ऐसा कुछ भी नहीं कर रहे हैं जो करी कार्यों से लाभान्वित हो और इसके बजाय पारंपरिक एकाधिक-तर्क कार्यों का उपयोग करना अधिक मुहावरेदार जावास्क्रिप्ट होगा। ऐसा करने से यह देखना आसान हो सकता है कि एक नज़र में क्या हो रहा है। आप const name = lambda-expressionफ़ंक्शन को परिभाषित करने के लिए भी उपयोग कर रहे हैं , जहां इसके function name (args) { ... }बजाय इसका उपयोग करना अधिक मुहावरेदार होगा । मुझे पता है कि वे शब्दार्थ से थोड़े अलग हैं, लेकिन जब तक आप उन अंतरों पर भरोसा नहीं करते हैं, तब तक मैं संभव हो तो अधिक सामान्य संस्करण का उपयोग करने का सुझाव दूंगा।


5
प्रकारों के लिए +1! सिर्फ इसलिए कि भाषा उनके पास नहीं है, इसका मतलब यह नहीं है कि आपको उनके बारे में सोचने की ज़रूरत नहीं है । ECMAScript के लिए कई प्रलेखन प्रणालियों के प्रकारों को रिकॉर्ड करने के लिए एक प्रकार की भाषा है। कई ECMAScript IDE के साथ-साथ एक प्रकार की भाषा भी होती है (और आमतौर पर, वे प्रमुख प्रलेखन प्रणालियों के लिए टाइप भाषा भी समझते हैं), और वे उन प्रकार के एनोटेशन का उपयोग करके अल्पविकसित प्रकार की जाँच और हेयुरिस्टिक हिंटिंग भी कर सकते हैं
जोर्ज डब्ल्यू मित्तग

आपने मुझे चबाने के लिए बहुत कुछ दिया है: मुहावरों का उपयोग करके, प्रकार की परिभाषाएँ, सार्थक नाम ... धन्यवाद! कई संभावित टिप्पणियों में से कुछ: मैं निश्चित रूप से कुछ कार्यों को करी कार्यों के रूप में लिखने का इरादा नहीं कर रहा था; उन्होंने सिर्फ उस तरह से विकसित किया जैसे मैंने लेखन के दौरान अपना कोड रिफैक्ट किया। अब मैं देख सकता हूँ कि इसकी आवश्यकता कैसे नहीं थी, और यहां तक ​​कि सिर्फ एक ही कार्य के लिए उन दो कार्यों के मापदंडों को दो मापदंडों में विलय करने से न केवल अधिक समझ में आता है, बल्कि तुरंत उस छोटे से कम से कम पठनीय बनाता है।
एंड्रयू विल्म्स

@ JörgWMittag, प्रकारों के महत्व के बारे में आपकी टिप्पणी और आपके द्वारा लिखे गए अन्य उत्तर के लिंक के लिए धन्यवाद। मैं WebStorm का उपयोग करता हूं और मुझे यह महसूस नहीं हुआ कि मैंने आपके अन्य उत्तर को कैसे पढ़ा, इसके अनुसार, WebStorm जानता है कि jsdoc जैसी टिप्पणियों की व्याख्या कैसे करें। मैं आपकी टिप्पणी से यह मान रहा हूं कि jsdoc और WebStorm का उपयोग कार्यात्मक के लिए एक साथ किया जा सकता है, न केवल अनिवार्य, कोड के लिए, बल्कि मुझे वास्तव में यह जानने के लिए और अधिक प्रलाप करना होगा। मैंने पहले और अब jsdoc के साथ खेला है, मुझे पता है कि WebStorm और मैं इसमें सहयोग कर सकते हैं, मुझे उम्मीद है कि मैं उस सुविधा / दृष्टिकोण का उपयोग करूंगा।
एंड्रयू विल्म्स

@ जूल्स, केवल यह स्पष्ट करने के लिए कि मैं अपनी टिप्पणी में किस करी फ़ंक्शन का उल्लेख कर रहा था: जैसा कि आपने निहित किया है, प्रत्येक उदाहरण को obj => key => ...सरल किया जा सकता है (obj, key) => ...क्योंकि बाद में getX(obj)(key)भी इसे सरल बनाया जा सकता है get(obj, key)। इसके विपरीत, एक और क्यूरेटेड फ़ंक्शन, (getX, filter = vals => vals) => (objA, objB) => ...आसानी से सरलीकृत नहीं किया जा सकता है, कम से कम बाकी कोड के संदर्भ में जैसा कि लिखा गया है।
एंड्रयू विल्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.