रूबी 2.0 आलसी Enumerable

हाल ही में, मेरे Enumerable::Lazy पैच को माणिक ट्रंक में अपनाया गया था। और इसका मतलब है कि रूबी 2.0 में हम कर सकते हैं:
 a = [1,2,3,4,2,5].lazy.map { |x| x * 10 }.select { |x| x > 30 } #=>    a.to_a #=> [40, 50],     . 

क्यों?


रूबी सुंदर है, और एक अनिवार्य भाषा के रूप में, फिर भी यह हमें सुरुचिपूर्ण "कार्यात्मक" कोड लिखने की अनुमति देती है। उदाहरण के लिए:
 data.map(&:split).map(&:reverse) 

यह देखने में बहुत अधिक पठनीय है:
 data.map { |l| l.split.reverse } 

हालांकि, पहला उदाहरण अक्षम है: data परिवर्तित data , रूबी दो बार डेटा के माध्यम से गुजरता है, एक मध्यवर्ती सरणी पैदा करता है। जब तक डेटा की मात्रा छोटी है, तब तक यह कमी महत्वपूर्ण नहीं है। लेकिन मान लीजिए कि हम एक बड़ी फाइल को पार्स करते हैं:
 File.open("text") do |f| f.each.flat_map(&:split).grep(/ruby/) end 

इस मामले में, सरणी के माध्यम से एक डबल पास की अनुमति नहीं है। सौभाग्य से, Enumerable::Lazy के आगमन के साथ Enumerable::Lazy यह अब कोई समस्या नहीं है। Enumerable::Lazy class में ओवरराइड किया गया map , select और कई अन्य फ़ंक्शन के आलसी संस्करण तुरंत गणना नहीं करते हैं। इसके बजाय, वे Enumerable::Lazy का एक उदाहरण देते हैं Enumerable::Lazy , आपको आलसी गणनाओं की श्रृंखला बनाने की अनुमति देता है।

अद्यतन

अब जब एन्युमरेटर :: आलसी पर अधिकांश काम समाप्त हो गया है, तो मैंने यह परीक्षण करने का निर्णय लिया कि यह वास्तव में किस तरह का लाभ देता है। परिणाम निराशाजनक थे (पहली बार मैंने बेंचमार्क में गलती की - इसलिए नीचे से आशावादी कोमेन्ट)। नियमित रूप से सरणियों पर संचालन की तुलना में औसतन एक आलसी एन्यूमरेटर 4 (!) टाइम्स धीमा है। यह इस तथ्य के कारण है कि जब एक आलसी श्रृंखला की गणना करते हैं, तो एक ब्लॉक आह्वान ऑपरेशन बहुत बार होता है। Ruby-lang.org पर चर्चा देखें । इसके बावजूद - एक आलसी एन्यूमरेटर के लाभ समान हैं - यह एक सुरुचिपूर्ण और पठनीय कोड है, जबकि धीमेपन के कारण उपयोगकर्ता मामलों की संख्या कम हो जाती है।
युसुके एंडोह द्वारा उद्धृत एक अच्छा उदाहरण:
 a = [] Prime.each do |x| next if x % 4 != 3 a << x break if a.size == 10 end 

आलस्य का उपयोग करने में बदल जाता है:
 Prime.lazy.select {|x| x % 4 == 3 }.take(10).to_a 

कब?


यह कहना मुश्किल है कि रूबी में Enumerator साथ आलसी कंप्यूटिंग को कैसे लागू किया जा सकता है, यह सोचने वाले पहले व्यक्ति थे। शायद 2008 की यह पोस्ट पहले में से एक थी। कार्यान्वयन का विचार सरल है और एन्यूमरेटर वस्तुओं की उल्लेखनीय संपत्ति पर आधारित है - उन्हें जंजीरों में जोड़ा जा सकता है। तब से, कई लेख प्रकाशित किए गए हैं , और विचार की व्याख्या करने वाली टिप्पणियों को enumerator.c में जोड़ा गया है। रूबी-लैंग पर चर्चा तीन साल से अधिक समय तक चली, और आखिरकार मात्ज़ ने यता हारा को लागू करने के लिए मतदान किया।
इसने Enumerable::lazy method को प्रस्तावित किया, जो एक Enumerable::Lazy में सरणी को "लपेटता" है Enumerable::Lazy उदाहरण, जो बदले में, एक आलसी श्रृंखला बनाने के लिए उपयोग किया जाता है। सी के लिए पैच अनुरोध को आवाज दी गई और मैंने तुरंत कॉल स्वीकार कर लिया। वैसे, मैं हाल ही में कार्यात्मक प्रोग्रामिंग में रुचि रखता था, और रूबी के आंतरिक क्षेत्रों में खुदाई बहुत दिलचस्प थी। नतीजतन, मुझे पूल का अनुरोध करने के लिए सम्मानित किया गया था, जो कि थोड़े समय के बाद, कुछ दिनों पहले अपनाया गया था। वे इसे ट्रंक में भरते हैं और इसे रूबी 2.0 ( रोडमैप देखें ) में जारी किया जाएगा।

कैसे?


एन्यूमरेटर (छोड़ें यदि आप जानते हैं)

उन लोगों के लिए जिन्हें इस बात की जानकारी नहीं है कि एक नियमित एन्यूमरेटर क्या है और इसके साथ क्या खाया जाता है:
 enum = [1, 2].each puts enum.next #=> 1 puts enum.next #=> 2 puts enum.next #=> StopIteration exception raised enum = Enumerator.new do |yielder| yielder << 1 yielder << 2 end puts enum.next #=> 1 puts enum.next #=> 2 puts enum.next #=> StopIteration exception raised enum = "xy".enum_for(:each_byte) enum.each { |b| puts b } # => 120 # => 121 o = Object.new def o.each yield yield 'hello' yield [1, 2] end enum = o.to_enum p enum.next #=> nil p enum.next #=> 'hello' p enum.next #=> [1, 2] enum = %w{foo bar baz}.map puts enum.with_index { |w, i| "#{i}:#{w}" } # => ["0:foo", "1:bar", "2:baz"] #     a = [1, 2, 3] some_method(a.enum_for) [1,2,3].cycle.take(10) #=> [1, 2, 3, 1, 2, 3, 1, 2, 3, 1] 

जैसा कि आप शायद पहले ही अनुमान लगा चुके हैं, #to_enum और #enum_for Kernel मॉड्यूल में स्थित हैं, जिसका अर्थ है कि वे किसी भी वस्तु के लिए सुलभ हैं। उदाहरणों को enumerator.c से लिया जाता है, यदि आप इसे यहाँ देख सकते हैं: test / ruby ​​/ test_enumerator.rb । Enumrator के इंटर्नल शायद एक अलग पोस्ट के लायक हैं, लेकिन यह ध्यान देने योग्य है कि यह सब जादू next फाइबर के साथ लागू किया गया है।

आलसी व्यक्ति

यह समझने के लिए कि कैसे असंख्य :: आलसी काम करता है, बस इस कोड को देखें:
 module Enumerable class Lazy < Enumerator def initialize(obj) super() do |yielder| obj.each do |val| if block_given? yield(yielder, val) else yielder << val end end end end def map Lazy.new(self) do |yielder, val| yielder << yield(val) end end end end a = Enumerable::Lazy.new([1,2,3]) a = a.map { |x| x * 10 }.map { |x| x - 1 } puts a.next #=> 9 puts a.next #=> 19 

यह एक विशिष्ट रूबी कार्यान्वयन है जिसे यूटाका द्वारा प्रस्तावित किया गया है। लेकिन ध्यान दें - इस उदाहरण में मैं विधि के पैरामीटर के रूप में &block प्रोक और कॉल call कनवर्ट करने के लिए उपयोग नहीं करता हूं। इसके बजाय, हम each लिए सीधे ब्लॉक के अंदर yield सकते each ! block_given? उम्मीद के मुताबिक काम भी करता है। इसके अलावा, जबकि ब्लॉक के अंदर, हम अभी भी self (जैसे return ) कह सकते हैं। महान, है ना? इस विषय पर एक अद्भुत पोस्ट Yehuda Katz ( एक और ) से।
कोड स्वयं काफी सरल है, लेकिन आइए बारीकी से देखें: जैसा कि पहले ही उल्लेख किया गया है, मुख्य विचार एक श्रृंखला का निर्माण करना है। इसलिए, Enumerable::Lazy#map Enumerable::Lazy का एक नया उदाहरण बनाता है Enumerable::Lazy इसे एक तर्क के रूप में "पिछले" ऑब्जेक्ट को पास करता है। जब to_a कहा जाता है ( each , एक ब्लॉक, take , आदि के साथ), गणना की जाती है: रूबी श्रृंखला (छवि 1) की शुरुआत में वापस आती है, मूल वस्तु से अगला मूल्य प्राप्त करता है और इसे बाहर की ओर लौटाता है, आलसी तरीकों के ब्लॉक के साथ क्रमिक रूप से संशोधित करता है (में) उसी क्रम में जिसमें वे "त्रिशंकु" थे)।

अंजीर। 1 आलसी प्रगंडकों की श्रृंखला।

सी पैच

मेरा सी पैच रूबी कार्यान्वयन को पूरी तरह से दोहराता है। इस तथ्य को छोड़कर कि मैं lazy_initialize अंदर super सीधे कॉल करने के बजाय lazy_initialize मैं एक जनरेटर ( Enumerator::Generator ) lazy_initialize हूं और आरंभ करता हूं, जिसे Enumerator::Generator तर्क के रूप में पास किया जाता है।
अंतिम पैच में, nobu ने कोड को थोड़ा सा lazy_init_block_i : उन्होंने ब्लॉक-फ़ंक्शन के अंदर if-else की स्थिति को दो अलग-अलग तरीकों ( lazy_init_block_i और lazy_init_block ) में lazy_init_block , और lazy_initialize करने के लिए शर्तों को स्थानांतरित कर lazy_initialize
इसके अलावा, रूबी सरणी को एक ब्लॉक फ़ंक्शन के पैरामीटर के रूप में पारित करने के बजाय, यह मापदंडों के साथ एक नियमित सी सरणी का निर्माण करता है। तदनुसार, अब आपको yielder और ब्लॉक के अंदर मान प्राप्त करने के लिए rb_ary_entry का उपयोग करने की आवश्यकता नहीं है।
उदाहरण के लिए, यह:
 static VALUE lazy_map_func(VALUE val, VALUE m, int argc, VALUE *argv) { VALUE result = rb_funcall(rb_block_proc(), id_call, 1, rb_ary_entry(val, 1)); return rb_funcall(rb_ary_entry(val, 0), id_yield, 1, result); } 

इस में बदल गया:
 static VALUE lazy_map_func(VALUE val, VALUE m, int argc, VALUE *argv) { VALUE result = rb_yield_values2(argc - 1, &argv[1]); return rb_funcall(argv[0], id_yield, 1, result); } 

सच कहूँ तो, मैं रूबी इंटर्नल में एक पूर्ण शुरुआत थी, इसलिए इसे काफी तुच्छ पुल अनुरोध लिखने में दो सप्ताह का समय लगा। इसके अलावा, पहली बार में मैंने एक अलग रास्ता अपनाने की कोशिश की: अपने आप में एक प्रक्रिया के रूप में आलसी तरीकों के ब्लॉक को बचाने की कोशिश की। यहां देखने के लिए पहला क्रेजी पुल अनुरोध । जब Enumerator#next को बुलाया गया, तो अगले मूल्य पर एक के बाद एक सभी प्रक्रियाएं "लागू" की गईं। आलसी map और select ठीक काम किया है, लेकिन जब मैंने Enumerator#each करने की कोशिश की, तो मुझे एहसास हुआ कि यह किसी भी तरह (या नहीं?) था।
आप बस यहां कठिन हो गए हैं, इसलिए यदि आप रूबल में कुछ पैच करने की योजना बनाते हैं, तो महान लेखों का एक गुच्छा है । एक बोनस के रूप में, हमें आलसी क्यों होना चाहिए, इस बारे में एक और लेख है

निष्कर्ष


अब Enumerator::Lazy पास अपने शस्त्रागार select , map , reject , grep (पहले पैच द्वारा जोड़ा गया), flat_map , zip , take , take_while , drop , drop_while , cycle (हाल ही में जोड़ा गया शूगो ) है। एपीआई का विकास और चर्चा जारी है, लेकिन अगर आप अभी आलसी होना चाहते हैं, तो बस रूबी को ट्रंक से निकालें और आनंद लें।

नोट:

PS यह अंग्रेजी के मेरे लेख का अनुवाद है।

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


All Articles