फाइबर एक ऐसी चीज है जिसका आप शायद कभी भी आवेदन-स्तर के कोड में सीधे उपयोग नहीं करेंगे। वे एक प्रवाह-नियंत्रण आदिम हैं जो आप अन्य अमूर्त बनाने के लिए उपयोग कर सकते हैं, जो आप तब उच्च-स्तरीय कोड में उपयोग करते हैं।
संभवतः रूबी में फाइबर का # 1 उपयोग Enumerator
एस को लागू करने के लिए है , जो रूबी 1.9 में एक कोर रूबी वर्ग हैं। ये अविश्वसनीय रूप से उपयोगी हैं।
रूबी 1.9 में, यदि आप कोर वर्गों पर लगभग किसी भी पुनरावृत्त विधि को कॉल करते हैं, तो बिना किसी खंड को पास किए , यह वापस आ जाएगी Enumerator
।
irb(main):001:0> [1,2,3].reverse_each
=> #<Enumerator: [1, 2, 3]:reverse_each>
irb(main):002:0> "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):003:0> 1.upto(10)
=> #<Enumerator: 1:upto(10)>
ये Enumerator
एन्यूमरेबल ऑब्जेक्ट्स हैं, और उनके each
तरीकों से उन तत्वों की उपज होती है जो मूल इट्रेटर विधि द्वारा उपजते थे, इसे एक ब्लॉक के साथ कहा जाता था। मेरे द्वारा दिए गए उदाहरण में, Enumerator द्वारा लौटाया गया reverse_each
एक each
तरीका है जो 3,2,1 पैदावार देता है। एन्युमरेटर chars
"सी", "बी", "ए" (और इसी तरह) पैदावार करके लौटा । लेकिन, मूल पुनरावृत्ति विधि के विपरीत, एन्यूमरेटर तत्वों को एक-एक करके वापस कर सकता है यदि आप next
इसे बार-बार कहते हैं:
irb(main):001:0> e = "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):002:0> e.next
=> "a"
irb(main):003:0> e.next
=> "b"
irb(main):004:0> e.next
=> "c"
आपने "आंतरिक पुनरावृत्तियों" और "बाहरी पुनरावृत्तियों" के बारे में सुना होगा (दोनों का एक अच्छा वर्णन "गैंग ऑफ़ फोर" डिज़ाइन पैटर्न बुक में दिया गया है)। उपरोक्त उदाहरण से पता चलता है कि Enumerators का उपयोग किसी बाहरी में आंतरिक पुनरावृत्ति को चालू करने के लिए किया जा सकता है।
यह आपके अपने एन्यूमरेटर्स बनाने का एक तरीका है:
class SomeClass
def an_iterator
# note the 'return enum_for...' pattern; it's very useful
# enum_for is an Object method
# so even for iterators which don't return an Enumerator when called
# with no block, you can easily get one by calling 'enum_for'
return enum_for(:an_iterator) if not block_given?
yield 1
yield 2
yield 3
end
end
चलो यह कोशिश करते हैं:
e = SomeClass.new.an_iterator
e.next # => 1
e.next # => 2
e.next # => 3
एक मिनट रुको ... वहाँ कुछ भी अजीब लगता है? आपने yield
कथनों an_iterator
को स्ट्रेट-लाइन कोड के रूप में लिखा था, लेकिन Enumerator उन्हें एक बार में चला सकता है । कॉल के बीच में next
, का निष्पादन an_iterator
"जमे हुए" है। हर बार जब आप कॉल करते हैं next
, तो यह निम्न yield
स्टेटमेंट तक जारी रहता है , और फिर "फ़्रीज़" होता है।
क्या आप अंदाजा लगा सकते हैं कि इसे कैसे लागू किया जाता है? एन्यूमरेटर an_iterator
एक फाइबर में कॉल को लपेटता है , और एक ब्लॉक पास करता है जो फाइबर को निलंबित करता है । तो हर बार an_iterator
ब्लॉक करने के लिए पैदावार, जिस फाइबर पर चल रहा है वह निलंबित है, और निष्पादन मुख्य धागे पर जारी है। अगली बार जब आप कॉल करते हैं next
, तो यह फाइबर को नियंत्रित करता है, ब्लॉक वापस लौटता है , और an_iterator
जहां इसे छोड़ दिया जाता है , वहां जारी रहता है।
यह सोचने के लिए शिक्षाप्रद होगा कि बिना रेशों के ऐसा करने के लिए क्या करना होगा। हर वर्ग जो आंतरिक और बाहरी दोनों पुनरावृत्तियों को प्रदान करना चाहता था, को कॉल के बीच राज्य का ट्रैक रखने के लिए स्पष्ट कोड रखना होगा next
। इसके बाद प्रत्येक कॉल को उस स्थिति की जांच करनी होगी, और मूल्य वापस करने से पहले इसे अपडेट करना होगा। तंतुओं के साथ, हम किसी भी आंतरिक पुनरावृत्ति को स्वचालित रूप से बाहरी में बदल सकते हैं ।
इसका मतलब फ़ाइबर फ़ाइले से नहीं है, लेकिन मुझे एनुमेरेटर्स के साथ एक और बात का उल्लेख करने दें: वे आपको अन्य इतरेटर्स के अलावा उच्च-क्रम वाले एन्युमरेबल तरीकों को लागू करने की अनुमति देते हैं each
। इसके बारे में सोचो: सामान्य रूप से सभी Enumerable विधियों, सहित map
, select
, include?
, inject
, और इतने पर, सभी तत्वों पर काम द्वारा उत्पन्न होने वाले each
। लेकिन क्या होगा अगर एक वस्तु के अलावा अन्य पुनरावृत्तियों है each
?
irb(main):001:0> "Hello".chars.select { |c| c =~ /[A-Z]/ }
=> ["H"]
irb(main):002:0> "Hello".bytes.sort
=> [72, 101, 108, 108, 111]
अवरोधक को बिना किसी ब्लॉक के कॉल करना एक एन्यूमरेटर लौटाता है, और फिर आप उस पर अन्य एन्यूमरेबल तरीकों को कॉल कर सकते हैं।
तंतुओं पर वापस जाना, क्या आपने take
एनुमेरबल से विधि का उपयोग किया है ?
class InfiniteSeries
include Enumerable
def each
i = 0
loop { yield(i += 1) }
end
end
अगर कुछ भी उस each
पद्धति को कॉल करता है, तो ऐसा लगता है कि इसे कभी वापस नहीं आना चाहिए, है ना? इसकी जांच करें:
InfiniteSeries.new.take(10) # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
मुझे नहीं पता कि यह हुड के नीचे फाइबर का उपयोग करता है, लेकिन यह हो सकता है। श्रृंखला की अनंत सूचियों और आलसी मूल्यांकन को लागू करने के लिए फाइबर का उपयोग किया जा सकता है। Enumerators के साथ परिभाषित कुछ आलसी तरीकों के उदाहरण के लिए, मैंने कुछ को यहां परिभाषित किया है: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
आप तंतुओं का उपयोग करके एक सामान्य-उद्देश्य वाले कोरटाइन सुविधा का निर्माण भी कर सकते हैं। मैंने अभी तक अपने किसी भी कार्यक्रम में कोरआउट का उपयोग नहीं किया है, लेकिन यह जानना एक अच्छी अवधारणा है।
मुझे उम्मीद है कि यह आपको संभावनाओं का कुछ विचार देगा। जैसा कि मैंने शुरुआत में कहा, फाइबर एक निम्न-स्तरीय प्रवाह-नियंत्रण आदिम हैं। वे आपके प्रोग्राम के भीतर कई कंट्रोल-फ्लो "पोज़िशन्स" को बनाए रखना संभव बनाते हैं (जैसे किताब के पन्नों में अलग-अलग "बुकमार्क") और उनके बीच इच्छानुसार स्विच करें। चूंकि मनमाना कोड एक फाइबर में चल सकता है, आप एक फाइबर पर 3-पार्टी कोड में कॉल कर सकते हैं, और फिर इसे "फ्रीज" कर सकते हैं और जब आप नियंत्रण में कोड में कॉल करते हैं तो यह कुछ और जारी रखता है।
कुछ इस तरह की कल्पना करें: आप एक सर्वर प्रोग्राम लिख रहे हैं जो कई क्लाइंट्स की सेवा करेगा। एक क्लाइंट के साथ एक पूर्ण इंटरैक्शन में चरणों की एक श्रृंखला से गुजरना शामिल होता है, लेकिन प्रत्येक कनेक्शन क्षणिक होता है, और आपको कनेक्शन के बीच प्रत्येक क्लाइंट के लिए स्थिति को याद रखना होगा। (वेब प्रोग्रामिंग की तरह ध्वनि?)
उस स्थिति को स्पष्ट रूप से संग्रहीत करने के बजाय, और हर बार जब ग्राहक कनेक्ट होता है (यह देखने के लिए कि उन्हें क्या करना है अगला "चरण"), तो आप प्रत्येक ग्राहक के लिए एक फाइबर बनाए रख सकते हैं। क्लाइंट की पहचान करने के बाद, आप उनके फाइबर को पुनः प्राप्त करेंगे और इसे फिर से शुरू करेंगे। फिर प्रत्येक कनेक्शन के अंत में, आप फाइबर को निलंबित कर देंगे और इसे फिर से स्टोर करेंगे। इस तरह, आप एक संपूर्ण इंटरैक्शन के लिए सभी लॉजिकों को लागू करने के लिए स्ट्रेट-लाइन कोड लिख सकते हैं, जिसमें सभी चरण शामिल हैं (जैसे आप स्वाभाविक रूप से यदि आपका प्रोग्राम स्थानीय रूप से चलाने के लिए बनाया गया था)।
मुझे यकीन है कि ऐसे कई कारण हैं कि ऐसा व्यवहार व्यावहारिक नहीं हो सकता है (कम से कम अभी के लिए), लेकिन फिर से मैं आपको कुछ संभावनाएं दिखाने की कोशिश कर रहा हूं। कौन जाने; एक बार जब आप अवधारणा प्राप्त करते हैं, तो आप कुछ पूरी तरह से नए एप्लिकेशन के साथ आ सकते हैं, जो किसी और ने अभी तक नहीं सोचा है!