हमें तंतुओं की आवश्यकता क्यों है


101

फाइबर्स के लिए हमें क्लासिक उदाहरण मिला है: फाइबोनैचि संख्याओं का सृजन

fib = Fiber.new do  
  x, y = 0, 1 
  loop do  
    Fiber.yield y 
    x,y = y,x+y 
  end 
end

हमें यहां फाइबर्स की आवश्यकता क्यों है? मैं इसे केवल उसी प्रोक (बंद करने, वास्तव में) के साथ फिर से लिख सकता हूं

def clsr
  x, y = 0, 1
  Proc.new do
    x, y = y, x + y
    x
  end
end

इसलिए

10.times { puts fib.resume }

तथा

prc = clsr 
10.times { puts prc.call }

केवल एक ही परिणाम वापस आ जाएगा।

तो फाइबर के क्या फायदे हैं। फाइबर्स के साथ मैं किस तरह का सामान लिख सकता हूं मैं लैम्ब्डा और अन्य शांत रूबी सुविधाओं के साथ नहीं कर सकता हूं?


4
पुराना रिट्रेसमेंट उदाहरण सबसे खराब संभव प्रेरक है;; यहां तक ​​कि एक सूत्र भी है जिसका उपयोग करके आप O (1) में किसी भी संख्या की गणना कर सकते हैं ।
यूएसआर

17
यह समस्या एल्गोरिथ्म के बारे में नहीं है, लेकिन फाइबर को समझने के बारे में है :)
fl00r

जवाबों:


230

फाइबर एक ऐसी चीज है जिसका आप शायद कभी भी आवेदन-स्तर के कोड में सीधे उपयोग नहीं करेंगे। वे एक प्रवाह-नियंत्रण आदिम हैं जो आप अन्य अमूर्त बनाने के लिए उपयोग कर सकते हैं, जो आप तब उच्च-स्तरीय कोड में उपयोग करते हैं।

संभवतः रूबी में फाइबर का # 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-पार्टी कोड में कॉल कर सकते हैं, और फिर इसे "फ्रीज" कर सकते हैं और जब आप नियंत्रण में कोड में कॉल करते हैं तो यह कुछ और जारी रखता है।

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

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

मुझे यकीन है कि ऐसे कई कारण हैं कि ऐसा व्यवहार व्यावहारिक नहीं हो सकता है (कम से कम अभी के लिए), लेकिन फिर से मैं आपको कुछ संभावनाएं दिखाने की कोशिश कर रहा हूं। कौन जाने; एक बार जब आप अवधारणा प्राप्त करते हैं, तो आप कुछ पूरी तरह से नए एप्लिकेशन के साथ आ सकते हैं, जो किसी और ने अभी तक नहीं सोचा है!


धन्यवाद आपके उत्तर के लिए धन्यवाद! तो वे charsसिर्फ बंद होने के साथ क्यों लागू नहीं करते हैं ?
fl00r

@ fl00r, मैं और भी अधिक जानकारी जोड़ने के बारे में सोच रहा हूं, लेकिन मुझे नहीं पता कि क्या यह उत्तर पहले से ही लंबा है ... क्या आप अधिक चाहते हैं?
एलेक्स डी

13
यह उत्तर इतना अच्छा है कि इसे कहीं ब्लॉग पोस्ट के रूप में लिखा जाना चाहिए, मेथिंक।
जेसन वोएगले

1
अद्यतन: ऐसा लगता है कि Enumerableरूबी 2.0 में कुछ "आलसी" तरीके शामिल होंगे।
एलेक्स डी

2
takeएक फाइबर की आवश्यकता नहीं है। इसके बजाय, takeबस एन-वें उपज के दौरान टूट जाता है। जब एक ब्लॉक के अंदर उपयोग किया जाता है, breakतो ब्लॉक को परिभाषित करने वाले फ्रेम पर नियंत्रण लौटाता है। a = [] ; InfiniteSeries.new.each { |x| a << x ; break if a.length == 10 } ; a
मैथ्यू

22

क्लोजर के विपरीत, जिसमें एक निर्धारित प्रवेश और निकास बिंदु होता है, फाइबर कई बार अपनी स्थिति और वापसी (उपज) को संरक्षित कर सकते हैं:

f = Fiber.new do
  puts 'some code'
  param = Fiber.yield 'return' # sent parameter, received parameter
  puts "received param: #{param}"
  Fiber.yield #nothing sent, nothing received 
  puts 'etc'
end

puts f.resume
f.resume 'param'
f.resume

इसे प्रिंट करता है:

some code
return
received param: param
etc

अन्य रूबी सुविधाओं के साथ इस तर्क का कार्यान्वयन कम पठनीय होगा।

इस सुविधा के साथ, अच्छा फाइबर का उपयोग मैनुअल कोऑपरेटिव शेड्यूलिंग (थ्रेड्स प्रतिस्थापन के रूप में) करना है। इलियाना ग्रिगोरिक के पास अतुल्यकालिक पुस्तकालय ( eventmachineइस मामले में) को बदलने के लिए एक अच्छा उदाहरण है कि एसिंक्रोनस निष्पादन के आईओ-शेड्यूलिंग के फायदे को खोने के बिना एक तुल्यकालिक एपीआई की तरह क्या दिखता है। यहाँ है लिंक


धन्यवाद! मैं डॉक्स पढ़ता हूं, इसलिए मैं इस सभी जादू को कई प्रविष्टियों के साथ समझता हूं और फाइबर के अंदर से बाहर निकलता हूं। लेकिन मुझे यकीन नहीं है कि यह सामान जीवन को आसान बनाता है। मुझे नहीं लगता कि यह अच्छा विचार है कि यह सभी रिज्यूमे और पैदावार का पालन करने की कोशिश कर रहा है। यह एक क्लेव की तरह दिखता है जो कि अनटंगल करना मुश्किल है। इसलिए मैं यह समझना चाहता हूं कि क्या ऐसे मामले हैं जहां फाइबर का यह समूह अच्छा समाधान है। ईवेंटमाचिन शांत है, लेकिन तंतुओं को समझने के लिए सबसे अच्छी जगह नहीं है, क्योंकि पहले आपको इस रिएक्टर पैटर्न की सभी चीजों को समझना चाहिए। इसलिए मुझे लगता है मैं फाइबर physical meaningको अधिक सरल उदाहरण में समझ सकता हूं
fl00r
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.