আমাদের ফাইবারের দরকার কেন?


100

ফাইবার্সের জন্য আমরা ক্লাসিক উদাহরণ পেয়েছি: ফিবোনাচি সংখ্যা তৈরি করা

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
পুরাতন ফিবোনাচি উদাহরণটি কেবলমাত্র সবচেয়ে খারাপ সম্ভাবনাময় প্রেরণা ;-) এমন একটি সূত্র রয়েছে যা আপনি ও (1) এর যে কোনও ফাইবোনাকির সংখ্যা গণনা করতে ব্যবহার করতে পারেন ।
usr ডিরেক্টরির

17
আপনার সমস্যাটি অ্যালগরিদম সম্পর্কে নয়, তবে তন্তুগুলি বোঝার জন্য :)
fl00r

উত্তর:


229

ফাইবার এমন একটি জিনিস যা আপনি সম্ভবত সরাসরি অ্যাপ্লিকেশন-স্তরের কোডটিতে ব্যবহার করবেন না। এগুলি একটি ফ্লো-কন্ট্রোল আদিম যা আপনি অন্য বিমূর্ততা তৈরি করতে ব্যবহার করতে পারেন, যা আপনি তারপরে উচ্চ-স্তরের কোডে ব্যবহার করেন।

সম্ভবত রুবির # 1 টি ফাইবারের ব্যবহারটি Enumeratorএস প্রয়োগ করতে হয় , যা রুবি ১.৯ এর মূল রুবি শ্রেণি। এগুলি অবিশ্বাস্যরূপে দরকারী।

রুবি ১.৯-এ, আপনি যদি কোনও ক্লাস ছাড়াই মূল ক্লাসগুলিতে প্রায় কোনও পুনরুক্তি পদ্ধতিতে কল করেন তবে এটি একটি ফিরে আসবে 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পদ্ধতিগুলি এমন উপাদান দেয় যা মূল পুনরুক্তি পদ্ধতি দ্বারা উত্পাদিত হত, যদি এটি একটি ব্লক দিয়ে ডাকা হত। আমি যে উদাহরণটি দিয়েছি, উদাহরণস্বরূপ, এনুমরেটর ফিরে এসেছে 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"

আপনি "অভ্যন্তরীণ পুনরাবৃত্তি" এবং "বহিরাগত পুনরাবৃত্তকারী" শুনেছেন (উভয়ের একটি ভাল বর্ণনা "গ্যাং অফ ফোর" ডিজাইন প্যাটার্নসের বইয়ে দেওয়া হয়েছে)। উপরের উদাহরণটি দেখায় যে অভ্যন্তরীণ পুনরাবৃত্তিকে একটি বাহ্যিক ক্ষেত্রে রূপান্তর করতে এনুমারেটর ব্যবহার করা যেতে পারে।

এটি আপনার নিজের গণক তৈরির এক উপায়:

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, তবে গণক সেগুলি একবারে চালাতে পারে । কল করার মধ্যবর্তী সময়ে next, কার্যকর করা an_iteratorহ'ল "হিমশীতল"। প্রতিবার আপনি যখন কল nextকরবেন তখন নীচের yieldবিবৃতিটিতে চলছে এবং তারপরে আবার "হিমশীতল" হয়ে যায়।

আপনি কী অনুমান করতে পারেন এটি কীভাবে বাস্তবায়িত হয়? গণক an_iteratorএকটি ফাইবারের সাথে কলটি গুটিয়ে রাখে এবং একটি ব্লক পাস করে যা ফাইবারকে স্থগিত করে । সুতরাং প্রতিবারে an_iteratorব্লকের ফলন, এটিতে যে ফাইবারটি চলছে তা স্থগিত করা হয়েছে এবং মূল থ্রেডে কার্যকর করা অব্যাহত রয়েছে। পরবর্তী বার আপনি যখন কল nextকরবেন তখন এটি ফাইবারের নিয়ন্ত্রণে চলে যায়, ব্লকটি ফিরে আসে এবং an_iteratorযেখানেই ছেড়ে যায় সেখানেই তা চালিয়ে যায়।

তন্তু ছাড়া এটি করার জন্য কী প্রয়োজন হবে তা ভেবে শিক্ষণীয় হবে। প্রতিটি শ্রেণি যা অভ্যন্তরীণ এবং বাহ্যিক উভয় পুনরাবৃত্তিকে সরবরাহ করতে চেয়েছিল তাদের কাছে কলগুলির মধ্যে রাষ্ট্রের ট্র্যাক রাখতে স্পষ্ট কোড থাকতে হবে next। পরবর্তী প্রতিটি কলকে সেই অবস্থাটি পরীক্ষা করতে হবে এবং কোনও মান ফেরত দেওয়ার আগে এটি আপডেট করতে হবে। ফাইবারের সাহায্যে আমরা কোনও অভ্যন্তরীণ পুনরুক্তিকে স্বয়ংক্রিয়ভাবে একটি বাহ্যিক রূপান্তর করতে পারি।

এই তন্তু দিয়ে persay যা করতে হবে না, কিন্তু আমাকে আরও একটি জিনিস আপনি তথ্যসংগ্রহকারী করতে পারেন উল্লেখ: তারা আপনি ছাড়া অন্য অন্য iterators উচ্চতর-অর্ডার গণনীয় পদ্ধতি প্রয়োগ করা যায় each। সেটা ভাবুন: সাধারনত সব গণনীয় পদ্ধতি, সহ 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]

আমি জানি না এটি হুডের নীচে তন্তু ব্যবহার করে কিনা, তবে তা পারে। আঁশগুলি অসীম তালিকাগুলি এবং একটি সিরিজের অলস মূল্যায়ন কার্যকর করতে ব্যবহার করা যেতে পারে। গণকের সাথে সংজ্ঞায়িত কিছু অলস পদ্ধতির উদাহরণের জন্য, আমি এখানে কিছু সংজ্ঞায়িত করেছি: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb

আপনি ফাইবার ব্যবহার করে একটি সাধারণ-উদ্দেশ্যে কর্টিন সুবিধা তৈরি করতে পারেন। আমি এখনও আমার কোনও প্রোগ্রামে কর্টিন ব্যবহার করি নি, তবে এটি জানা ভাল ধারণা।

আমি আশা করি এটি আপনাকে সম্ভাবনার বিষয়ে কিছু ধারণা দেয়। আমি প্রথমদিকে যেমন বলেছিলাম, তন্তুগুলি নিম্ন-স্তরের প্রবাহ-নিয়ন্ত্রণ আদিম। তারা আপনার প্রোগ্রামের মধ্যে একাধিক নিয়ন্ত্রণ-প্রবাহ "অবস্থানগুলি" বজায় রাখা সম্ভব করে তোলে (কোনও বইয়ের পৃষ্ঠাগুলিতে "বিভিন্ন" বুকমার্কগুলির মত)) এবং তাদের মধ্যে পছন্দসই পরিবর্তন করতে পারে। যেহেতু নির্বিচারে কোডটি একটি ফাইবারে চলতে পারে, তাই আপনি একটি ফাইবারের উপর তৃতীয় পক্ষের কোডে কল করতে পারেন এবং তারপরে এটি "হিমায়িত" করতে পারেন এবং যখন এটি আপনার নিয়ন্ত্রণের কোডটিতে ফিরে আসে তখন অন্য কিছু করা চালিয়ে যেতে পারেন।

এরকম কিছু কল্পনা করুন: আপনি একটি সার্ভার প্রোগ্রাম লিখছেন যা অনেক ক্লায়েন্টকে সহায়তা করবে। ক্লায়েন্টের সাথে একটি সম্পূর্ণ মিথস্ক্রিয়াটি বিভিন্ন পদক্ষেপের মধ্য দিয়ে যাওয়ার সাথে জড়িত, তবে প্রতিটি সংযোগ ক্ষণস্থায়ী এবং সংযোগের মধ্যে প্রতিটি ক্লায়েন্টের জন্য আপনাকে রাষ্ট্রের কথা মনে রাখতে হবে। (ওয়েব প্রোগ্রামিংয়ের মতো শব্দ?)

রাষ্ট্রটি স্পষ্টভাবে সংরক্ষণ করে, এবং প্রতি ক্লায়েন্টের সাথে সংযুক্ত হওয়ার পরে এটি পরীক্ষা করার পরিবর্তে (তাদের পরবর্তী "পদক্ষেপ" কী করতে হবে তা দেখার জন্য), আপনি প্রতিটি ক্লায়েন্টের জন্য একটি ফাইবার বজায় রাখতে পারেন। ক্লায়েন্ট সনাক্ত করার পরে, আপনি তাদের ফাইবারটি পুনরুদ্ধার করতে এবং এটি পুনরায় শুরু করতে পারেন। তারপরে প্রতিটি সংযোগের শেষে, আপনি ফাইবারটি স্থগিত করে আবার স্টোর করবেন। এই পদ্ধতিতে, আপনি সমস্ত পদক্ষেপ সহ (সম্পূর্ণরূপে যদি আপনার প্রোগ্রামটি স্থানীয়ভাবে চালানো হয় তবে আপনি স্বাভাবিকভাবেই চান) সম্পূর্ণ মিথস্ক্রিয়াটির জন্য সমস্ত যুক্তি বাস্তবায়নের জন্য আপনি সরল-লাইন কোড লিখতে পারেন।

আমি নিশ্চিত যে এ জাতীয় জিনিস ব্যবহারিক না হওয়ার কারণগুলির অনেকগুলি কারণ রয়েছে (কমপক্ষে আপাতত) তবে আবার আমি আপনাকে সম্ভাবনার কয়েকটি দেখানোর চেষ্টা করছি। কে জানে; একবার আপনি ধারণাটি পেয়ে গেলে আপনি কিছু সম্পূর্ণ নতুন অ্যাপ্লিকেশন নিয়ে আসতে পারেন যা অন্য কেউ এখনও ভাবেনি!


আপনার উত্তর fro ধন্যবাদ! তাহলে কেন তারা charsকেবল ক্লোজার দিয়ে বাস্তবায়ন বা অন্যান্য গণক ব্যবহার করে না ?
fl00r

@ fl00r, আমি আরও তথ্য যুক্ত করার কথা ভাবছি, তবে উত্তরটি ইতিমধ্যে খুব দীর্ঘ কিনা তা আমি জানি না ... আপনি কি আরও চান?
অ্যালেক্স ডি

13
এই উত্তরটি এত ভাল যে এটি কোথাও একটি ব্লগ পোস্ট হিসাবে লেখা উচিত, মিথথিক্স।
জেসন ভয়েগলে

1
আপডেট: দেখে মনে হচ্ছে Enumerableরুবি ২.০-তে কিছু "অলস" পদ্ধতি অন্তর্ভুক্ত থাকবে।
অ্যালেক্স

2
takeএকটি ফাইবার প্রয়োজন হয় না। পরিবর্তে, takeকেবলমাত্র N-th ফলনের সময় বিরতি হয়। যখন কোনও ব্লকের অভ্যন্তরে ব্যবহৃত হয়, 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এই ক্ষেত্রে) কীভাবে সিঙ্ক্রোনাস এপিআইয়ের মতো দেখায় তা অ্যাসিঙ্ক্রোনাস এক্সিকিউশনের আইও- শিডিউলিংয়ের সুবিধাগুলি হারাতে না পারার পক্ষে একটি ভাল উদাহরণ রয়েছে good লিঙ্কটি এখানে ।


ধন্যবাদ! আমি ডকগুলি পড়েছি, তাই আমি অনেক এন্ট্রি সহ এই সমস্ত যাদুটি বুঝতে পারি এবং ফাইবারের অভ্যন্তরে প্রস্থান করি। তবে আমি নিশ্চিত নই যে এই জিনিসগুলি জীবনকে সহজ করে তোলে। আমি মনে করি না যে এই সমস্ত জীবনবৃত্তান্ত এবং ফলনগুলি অনুসরণ করার চেষ্টা করা ভাল ধারণা। দেখে মনে হচ্ছে এমন কলাকুশলীর মতো, যা আঁকানো শক্ত। সুতরাং আমি বুঝতে চাই যে এমন কোনও ঘটনা আছে যেখানে এই ফাইবারগুলির ক্লা ভাল সমাধান রয়েছে। ইভেন্টম্যাশিনটি দুর্দান্ত তবে তন্তুগুলি বোঝার জন্য সেরা জায়গা নয়, কারণ প্রথমে আপনার চুল্লিগুলির এই সমস্ত ধরণের জিনিসগুলি বোঝা উচিত। সুতরাং আমি physical meaningআরও সহজ উদাহরণে ফাইবারগুলি বুঝতে পারি
fl00r
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.