छोरों के लिए क्या गलत है?

जावा में बंद होने की संभावित घटना चर्चा का एक गर्म विषय बन गई है। भाषा के आगामी संस्करण में क्लोजर जोड़ने के लिए एक प्रस्ताव तैयार किया जा रहा है, हालांकि, प्रस्तावित वाक्यविन्यास, भाषा की जटिलता के साथ मिलकर, कई जावा प्रोग्रामर द्वारा आलोचना की जाती है।

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

मुझे नहीं पता है कि लोगों को छोरों के लिए छुटकारा पाने का इतना जुनून का सपना क्या है। यह पहली या दूसरी बार भी नहीं है कि कंप्यूटर विज्ञान की दुनिया के सिद्धांतकारों ने उनके खिलाफ विद्रोह किया है।

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

लिस्टिंग 1. सबसे सरल बंद।
3.times {puts "Inside the times method."} 

परिणाम:
समय पद्धति के अंदर।
समय पद्धति के अंदर।
समय पद्धति के अंदर।

यहाँ समय ऑब्जेक्ट 3 की विधि है; यह क्लोजर कोड को तीन बार निष्पादित करता है।
 {puts "Inside the times method."} 
और एक बंद है। यह एक अनाम फ़ंक्शन है, जो समय पद्धति में पारित होने पर, स्ट्रिंग को "बार विधि के अंदर" प्रिंट करता है। यह कोड लूप के लिए इसके विकल्प की तुलना में बहुत स्पष्ट और सरल है:

लिस्टिंग 2. लूपबैक लूप।
 for i in 1..3 puts "Inside the times method." end 

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

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

हालांकि, इलियट वास्तव में एक महत्वपूर्ण सवाल उठाता है: "आप पाश से नफरत क्यों करते हैं"?

यहाँ एक आम उदाहरण है:

 double sum = 0; for (int i = 0; i < array.length; i++) { sum += array[i]; } 

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

क्या आप प्रमाण चाहेंगे? कृपया आपको इलियट लेख से निम्नलिखित उदाहरण दें:

 String s = ""; for (int i = 0; i < args.length; i++) { s += array[i]; } 

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

यदि आपके पास एक पंक्ति में लूप के लिए एक सरल लिखने का अवसर था, तो कम पात्रों के साथ, इसे पढ़ना बहुत आसान होगा। क्योंकि लेखन का एक छोटा रूप कोड में त्रुटियों की बहुत कम घटना की ओर जाता है, और जब वे दिखाई देते हैं, तो वे (मूल रूप से) पकड़ने में बहुत आसान होते हैं।

इस स्थिति में तड़क कैसे मदद कर सकता है? यहाँ पहला हास्केल उदाहरण है:

 total = sum array 

ठीक है, सब ठीक है, यह काफी उचित नहीं था। योग फ़ंक्शन क्लोजर का उपयोग नहीं करता है - यह दृढ़ संकल्प (अनुवादक के नोट: अंग्रेजी संस्करण गुना है) के माध्यम से परिभाषित किया गया है, जो बदले में क्लोजर का उपयोग करता है।

 total = foldl (+) 0 array 

यहाँ क्लोजर लागू करने का दूसरा उदाहरण है:

 s = concat array s = foldr (++) [] array 

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

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

यहाँ एक दृश्य व्याख्या है:

 s = foldl (+) 0 [1, 2, 3] = foldl (+) (0 + 1) [2, 3] = foldl (+) 1 [2, 3] = foldl (+) (1 + 2) [3] = foldl (+) 3 [3] = foldl (+) (3 + 3) [] = foldl (+) 6 [] = 6 

हास्केल कई कनवल्शन फ़ंक्शंस प्रदान करता है: फोल्ड अपने पहले तत्व से सूची को संसाधित करना शुरू कर देता है, और अंतिम तत्व तक क्रमिक रूप से गुजरता है; तह, इसके विपरीत, अंतिम तत्व से शुरू होता है, और पहले तत्व की ओर बढ़ता है। अन्य विकल्प भी हैं, लेकिन ये दोनों बुनियादी हैं।

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

हालांकि, हर चक्र सूची में एक तत्व का एक तह नहीं है। निम्नलिखित उदाहरण पर विचार करें:

 for (int i = 0; i < array.length; i++) { array[i] *= 2; } 

इस मामले में, हम रूपांतरण के साथ काम कर रहे हैं, कार्यात्मक भाषाओं में इसे मानचित्र कहा जाता है:

 new_array = map (*2) array 

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

लूप्स के लिए तीसरी तरह का फिल्टर लूप है। एक उदाहरण पर विचार करें:

 int tmp[] = new int [nums.length]; int j = 0; for (int i = 0; i < nums.length; i++) { if ((nums[i] % 2) == 1) { tmp[j] = nums[i]; j++; } } 

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

 odds = filter (\i -> (i `mod` 2) == 1) nums odds = filter odd nums --   

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

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

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

अंत में, इलियट ने कई कोर पर समानांतर कोड निष्पादन के विषय पर अपने हाथ हिलाए, और शिकायत की कि कोड
 3.times {...} 
यह लगभग निश्चित रूप से लूप के लिए समान से भी बदतर होगा। यह मुझे लगता है कि वह एक परिस्थिति को नजरअंदाज करता है। हां, ऐसी गणनाएं हैं जिन्हें क्रमिक रूप से किया जाना चाहिए, ऐसे भी हैं जो समानांतर में किए जा सकते हैं। लेकिन एक को दूसरे से अलग करना कंपाइलर के लिए एक मुश्किल काम है यदि आपके द्वारा उपयोग किया जाने वाला एकमात्र उपकरण लूप के लिए है। यदि आप अनुक्रमिक अभिकलन (यानी, तह और तह) और (संभवतः) समानांतर (मानचित्र और फ़िल्टर) को अलग कर सकते हैं, तो, इससे, आप संकलक के कार्य को सरल करते हैं। इसके अलावा, यदि आप संकलक की तुलना में बेहतर प्रक्रिया की प्रकृति को जानते हैं, तो आप स्पष्ट रूप से मानचित्र के एक सीरियल या समानांतर संस्करण का अनुरोध कर सकते हैं।

अनुवादक से


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

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


All Articles