কম্পিউটার টিউটোরিয়াল

রুবি ক্লোজার শিখুন: ব্লক, প্রক, এবং ল্যাম্বডা বোঝা

Ruby Magic-এ আমরা প্রতিদিন ব্যবহৃত জিনিসগুলোর পেছনের জাদুতে ডুবে যাওয়া পছন্দ করি যাতে বুঝতে পারি সেগুলো কীভাবে কাজ করে। এই সংস্করণে, আমরা ব্লক, প্রক এবং ল্যাম্বডার পার্থক্য এক্সপ্লোর করব।

ফার্স্ট-ক্লাস ফাংশনযুক্ত প্রোগ্রামিং ল্যাঙ্গুয়েজে, ফাংশনগুলো ভেরিয়েবলে সংরক্ষণ করা যায় এবং অন্য ফাংশনগুলোর আর্গুমেন্ট হিসেবে পাস করা যায়। ফাংশনগুলোแม.return value হিসেবে অন্য ফাংশনও ব্যবহার করতে পারে।

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

রুবিতে ফার্স্ট-ক্লাস ফাংশন নেই, কিন্তু ব্লক, প্রক এবং ল্যাম্বডা আকারে ক্লোজার আছে। ব্লকগুলো মেথডে কোড ব্লক পাস করার জন্য ব্যবহৃত হয়, এবং প্রক ও ল্যাম্বডা কোড ব্লক ভেরিয়েবলে সংরক্ষণ করতে দেয়।

ব্লক

রুবিতে, ব্লক হলো কোডের স্নিপেট যা পরে এক্সিকিউট করার জন্য তৈরি করা হয়। ব্লকগুলো মেথডে পাস করা হয় যা সেগুলো do এবং end কীওয়ার্ডের মধ্যে yield করে। উদাহরণের একটি হলো #each মেথড, যা enumারেবল অবজেক্টের ওপর লুপ করে।

[1,2,3].each do |n|
  puts "#{n}!"
end
[1,2,3].each { |n| puts "#{n}!" } # one-line equivalent

এই উদাহরণে, একটি ব্লক Array#each মেথডে পাস করা হয়, যা অ্যারেতে প্রতিটি আইটেমের জন্য ব্লক রান করে এবং কনসোলে প্রিন্ট করে।

def each
  i = 0
  while i < size
    yield at(i)
    i += 1
  end
end

Array#each-এর এই সরলীকৃত উদাহরণে, while লুপে, পাস করা ব্লক প্রতিটি আইটেমের জন্য এক্সিকিউট করার জন্য yield কল করা হয়। মনে রাখবেন এই মেথডের কোনো আর্গুমেন্ট নেই, কারণ ব্লকটি মেথডে ইম্প্লিসিটলি পাস করা হয়।

ইম্প্লিসিট ব্লক এবং yield কীওয়ার্ড

রুবিতে, মেথডগুলো ব্লক ইম্প্লিসিটলি এবং এক্সপ্লিসিটলি দুভাবেই নিতে পারে। ইম্প্লিসিট ব্লক পাসিং কাজ করে মেথডে yield কীওয়ার্ড কল করে। yield কীওয়ার্ড স্পেশাল। এটি পাস করা ব্লক খুঁজে বের করে এবং কল করে, তাই আপনাকে ব্লকটি মেথড যে আর্গুমেন্টগুলো একসেপ্ট করে সে লিস্টে যোগ করতে হয় না।

কারণ রুবি ইম্প্লিসিট ব্লক পাসিংয়ের অনুমতি দেয়, আপনি সব মেথডকে একটি ব্লক সহ কল করতে পারেন। যদি সেটি yield কল না করে, ব্লকটি উপেক্ষা করা হয়।

irb> "foo bar baz".split { p "block!" }
=> ["foo", "bar", "baz"]

যদি কল করা মেথড yield করে, পাস করা ব্লক খুঁজে বের করা হয় এবং yield কীওয়ার্ডে পাস করা যেকোনো আর্গুমেন্টসহ কল করা হয়।

def each
  return to_enum(:each) unless block_given?
  i = 0
  while i < size
    yield at(i)
    i += 1
  end
end

এই উদাহরণে একটি Enumerator ইনস্ট্যান্স রিটার্ন করে যদি ব্লক দেওয়া না থাকে।

yield এবং block_given? কীওয়ার্ডগুলো কারেন্ট স্কোপে ব্লক খুঁজে বের করে। এটি ইম্প্লিসিট ব্লক পাসিংয়ের অনুমতি দেয়, কিন্তু কোডকে ব্লকটি সরাসরি এক্সেস করতে দেয় না কারণ এটি কোনো ভেরিয়েবলে সংরক্ষিত নয়।

এক্সপ্লিসিটলি ব্লক পাস করা

আমরা একটি মেথডে এক্সপ্লিসিটলি একটি ব্লক এক্সেপ্ট করতে পারি এটি আর্গুমেন্ট হিসেবে যোগ করে একটি অ্যাম্পারস্যান্ড প্যারামিটার ব্যবহার করে (সাধারণত &block নামে ডাকা হয়)। যেহেতু ব্লকটি এখন এক্সপ্লিসিট, আমরা ফলাফল অবজেক্টে সরাসরি #call মেথড ব্যবহার করতে পারি yield-এর ওপর নির্ভর না করে।

&block আর্গুমেন্টটি একটি প্রপার আর্গুমেন্ট নয়, তাই এই মেথডকে ব্লক ছাড়া অন্য কিছু দিয়ে কল করলে ArgumentError প্রোডিউস হবে।

def each_explicit(&block)
  return to_enum(:each) unless block
  i = 0
  while i < size
    block.call at(i)
    i += 1
  end
end

যখন একটি ব্লক এইভাবে পাস করা হয় এবং একটি ভেরিয়েবলে সংরক্ষণ করা হয়, এটি অটোমেটিক্যালি একটি প্রক-এ কনভার্ট হয়।

প্রক

একটি "প্রক" হলো Proc ক্লাসের একটি ইনস্ট্যান্স, যা এক্সিকিউট করার জন্য একটি কোড ব্লক ধারণ করে, এবং একটি ভেরিয়েবলে সংরক্ষণ করা যায়। একটি প্রক তৈরি করতে, আপনি Proc.new কল করুন এবং এটিকে একটি ব্লক পাস করুন।

proc = Proc.new { |n| puts "#{n}!" }

যেহেতু একটি প্রক একটি ভেরিয়েবলে সংরক্ষণ করা যায়, এটি একটি নরমাল আর্গুমেন্টের মত একটি মেথডে পাস করা যায়। সেক্ষেত্রে, আমরা অ্যাম্পারস্যান্ড ব্যবহার করি না, কারণ প্রকটি এক্সপ্লিসিটলি পাস করা হয়।

def run_proc_with_random_number(proc)
  proc.call(random)
end
proc = Proc.new { |n| puts "#{n}!" }
run_proc_with_random_number(proc)

একটি প্রক তৈরি করে সেটি মেথডে পাস করার বদলে, আপনি রুবির অ্যাম্পারস্যান্ড প্যারামিটার সিনট্যাক্স ব্যবহার করতে পারেন যা আমরা আগে দেখেছিলাম এবং একটি ব্লক ব্যবহার করতে পারেন।

def run_proc_with_random_number(&proc)
  proc.call(random)
end
run_proc_with_random_number { |n| puts "#{n}!" }

মেথডে আর্গুমেন্টে যোগ করা অ্যাম্পারস্যান্ডটি লক্ষ্য করুন। এটি একটি পাস করা ব্লককে একটি প্রক অবজেক্টে কনভার্ট করবে এবং মেথড স্কোপে একটি ভেরিয়েবলে সংরক্ষণ করবে।

টিপ: যদিও মেথডে প্রক থাকা কিছু সिचুয়েশনে উপকারী, ব্লক থেকে প্রকে কনভারশন একটি পারফরম্যান্স হিট প্রোডিউস করে। যেকোনো সময় সম্ভব হলে, ইম্প্লিসিট ব্লক ব্যবহার করুন।

#to_proc

সিম্বল, হ্যাশ এবং মেথড #to_proc মেথড ব্যবহার করে প্রকে কনভার্ট করা যায়। এর একটি sık দেখা ব্যবহার হলো একটি সিম্বল থেকে তৈরি প্রক একটি মেথডে পাস করা।

[1,2,3].map(&:to_s)
[1,2,3].map {|i| i.to_s }
[1,2,3].map {|i| i.send(:to_s) }

এই উদাহরণটি অ্যারেতে প্রতিটি এলিমেন্টে #to_s কল করার তিনটি সমতুল্য উপায় দেখাচ্ছে। প্রথমটিতে, একটি সিম্বল, অ্যাম্পারস্যান্ড প্রিফিক্সড, পাস করা হয়, যা অটোমেটিক্যালি এর #to_proc মেথড কল করে প্রকে কনভার্ট করে। শেষ দুটি দেখাচ্ছে সেটি প্রক কীভাবে দেখতে পারে।

class Symbol
  def to_proc
    Proc.new { |i| i.send(self) }
  end
end

যদিও এটি একটি সরলীকৃত উদাহরণ, Symbol#to_proc-এর ইমপ্লিমেন্টেশন দেখাচ্ছে হাডের নিচে কী হচ্ছে। মেথডটি একটি প্রক রিটার্ন করে যা একটি আর্গুমেন্ট নেয় এবং সেটিকে self পাঠায়। যেহেতু এই কনটেক্স্টে self সিম্বল, এটি Integer#to_s মেথড কল করে।

ল্যাম্বডা

ল্যাম্বডা মূলত কিছু विशিষ্ট ফ্যাক্টরসহ প্রক। সেগুলো দুটি ভাবে "নরমাল" মেথডের মতো: সেগুলো কল করার সময় পাস করা আর্গুমেন্টের সংখ্যা এনফোর্স করে এবং "নরমাল" রিটার্ন ব্যবহার করে।

যখন একটি ল্যাম্বডাকে কল করা হয় যেটি একটি আর্গুমেন্ট এক্সপেক্ট করে কিন্তু ছাড়া, অথবা আপনি একটি আর্গুমেন্ট পাস করেন যেটি এক্সপেক্ট করে না, রুবি একটি ArgumentError রেইজ করে।

irb> lambda (a) { a }.call
ArgumentError: wrong number of arguments (given 0, expected 1)
from (irb):8:in `block in irb_binding'
from (irb):8
from /Users/jeff/.asdf/installs/ruby/2.3.0/bin/irb:11:in `
'

এছাড়া, একটি ল্যাম্বডা return কীওয়ার্ডকে একইভাবে ট्रीট করে যেভাবে একটি মেথড করে। একটি প্রক কল করলে, প্রোগ্রাম প্রকের কোড ব্লকে কন্ট্রোল yield করে। তাই, যদি প্রক রিটার্ন করে, কারেন্ট স্কোপও রিটার্ন করে। যদি একটি প্রক একটি ফাংশনের ভিতরে কল করা হয় এবং return কল করে, ফাংশনটি তৎক্ষণাৎ রিটার্ন করবে।

def return_from_proc
  a = Proc.new { return 10 }.call
  puts "This will never be printed."
end

এই ফাংশন প্রকে কন্ট্রোল yield করবে, তাই যখন সেটি রিটার্ন করবে, ফাংশনও রিটার্ন করবে। এই উদাহরণে ফাংশন কল করলে আউটপুট প্রিন্ট হবে না এবং 10 রিটার্ন করবে।

def return_from_lambda
  a = lambda { return 10 }.call
  puts "The lambda returned #{a}, and this will be printed."
end

ল্যাম্বডা ব্যবহার করলে, এটি প্রিন্ট হবে। ল্যাম্বডাতে return কল করা মেথডে return কল করার মতো আচরণ করবে, তাই a ভেরিয়েবলটি 10 দিয়ে পপুলেট হবে এবং লাইনটি কনসোলে প্রিন্ট হবে।

ব্লক, প্রক এবং ল্যাম্বডা

এখন যেহেতু আমরা ব্লক, প্রক এবং ল্যাম্বডার সব গভীরে গিয়েছি, চলুন জুম আউট করি এবং তুলনা সারাংশ করি।

  • ব্লক রুবিতে ব্যাপকভাবে ফাংশনগুলোর কোডের টুকরো পাস করার জন্য ব্যবহৃত হয়। yield কীওয়ার্ড ব্যবহার করে, একটি ব্লক ইম্প্লিসিটলি পাস করা যায় প্রকে কনভার্ট করা ছাড়া।
  • অ্যাম্পারস্যান্ড প্রিফিক্সড প্যারামিটার ব্যবহার করলে, একটি ব্লক মেথডে পাস করলে মেথডের কনটেক্স্টে একটি প্রক ফলাফল হিসেবে আসে। প্রকগুলো ব্লক-এর মত আচরণ করে, কিন্তু সেগুলো একটি ভেরিয়েবলে সংরক্ষণ করা যায়।
  • ল্যাম্বডা হলো প্রক যেগুলো মেথডের মত আচরণ করে, অর্থাৎ সেগুলো-arity এনফোর্স করে এবং তাদের প্যারেন্ট স্কোপের বদলে মেথডের মত রিটার্ন করে।

এটি রুবিতে ক্লোজারrances আমাদের লুককে সমাপ্ত করে। ক্লোজারrances সম্পর্কে আরও শিখতে আছে যেন লেক্সিক্যাল স্কোপ এবং বাইন্ডিং, কিন্তু সেগুলো আমরা ভবিষ্যতের épisodes-র জন্য রাখব। মিন্টিম, দয়া করে আমাদের জানান আপনি Ruby Magic-এর ভবিষ্যতের ইনস্টলমেন্টে, ক্লোজারrances বা অন্যদিকে, কী পড়তে চান @AppSignal-এ।

  1. RuboCop দিয়ে রুবি কোড কোয়ালিটি উন্নত: লিন্টিং ও অটো-ফরম্যাটিং

  2. রুবিতে ল্যাম্বডা মাস্টার করুন: বেসিক থেকে অ্যাডভান্সড টেকনিক

  3. রুবিতে freeze এবং frozen কবে ব্যবহার করবেন: ইমিউটেবিলিটির সেরা অনুশীলন

  4. রুবি লগিং মাস্টারি: Logger এবং Lograge-এ এক্সপার্ট গাইড