মেমোইজেশন হলো একটি টেকনিক जिससे আপনি আপনার অ্যাকসেসর মেথডগুলো দ্রুত করতে পারেন। একবারই করার যোজনীয় টাইম-কনসামিং কাজের ফলাফল ক্যাশে করে রাখে। রেইলসে মেমোইজেশন এতটাই ব্যবহার হয় যে এতে একটি মডিউল ছিল যা মেথডগুলো অটোমেটিক্যালি মেমোইজ করত।
বादে এটি বিতর্কিতভাবে রিমুভ করা হয় সবচেয়ে সাধারণ মেমোইজেশন প্যাটার্নের ফेवर করে। তবে যেমন দেখবেন, কিছু জায়গায় এই বেসিক প্যাটার্ন ঠিকমতো কাজ করে না। তাই আমরা অ্যাডভান্সড মেমোইজেশন প্যাটার্নগুলোও দেখব এবং রুবি সম্পর্কে কিছু মজার জিনিস শিখব!
সুপার বেসিক মেমোইজেশন
রুবিতে এই মেমোইজেশন প্যাটার্নটি সব जगह দেখবেন:
class User < ActiveRecord::Base def twitter_followers # assuming twitter_user.followers makes a network call @twitter_followers ||= twitter_user.followers end end ||= মোটামুটি @twitter_followers = @twitter_followers || twitter_user.followers এ অনুবাদ হয়। মানে প্রথমবার twitter_followers কল করলে নেটওয়ার্ক কল হবে, এরপর থেকে শুধু ইনস্ট্যান্স ভেরিয়েবল @twitter_followers-র ভ্যালু রিটার্ন হবে।
মাল্টি-লাইন মেমোইজেশন
কখনো কখনো স্লো কোড এক লাইনে ফিট হয় না যদি ভয়ঙ্কর কিছু না করি। বেসিক প্যাটার্নকে মাল্টি-লাইনে এক্সটেন্ড করার কয়েকটি উপায় আছে, আমার পছন্দেরটা:
class User < ActiveRecord::Base def main_address @main_address ||= begin maybe_main_address = home_address if prefers_home_address? maybe_main_address = work_address unless maybe_main_address maybe_main_address = addresses.first unless maybe_main_address end end end begin...end রুবিতে একটি কোড ব্লক তৈরি করে যেটাকে একক জিনিস হিসেবে বরতা যায়, সি-স্টাইল ল্যাঙ্গুয়েজের {...} এর মতো। তাই ||= এখানেও আগের মতো কাজ করে।
nil হলে কী?
এই মেমোইজেশন প্যাটার্নগুলোর একটা খারাপ, লুকানো সমস্যা আছে। প্রথম উদাহরণে, যদি ইউজারের টুইটার অ্যাকাউন্ট না থাকে এবং API nil রিটার্ন করে? দ্বিতীয়ে, যদি কোনো অ্যাড্রেস না থাকে এবং ব্লক nil রিটার্ন করে?
প্রতিবার মেথড কল করলে ইনস্ট্যান্স ভেরিয়েবল nil থাকবে, আর আমরা বারবার এক্সপেনসিভ ফেচ করব।
তাই ||= সঠিক পথ নয়। বরং nil এবং undefined-র পার্থক্য করতে হবে:
class User < ActiveRecord::Base def twitter_followers return @twitter_followers if defined? @twitter_followers @twitter_followers = twitter_user.followers end end class User < ActiveRecord::Base def main_address return @main_address if defined? @main_address @main_address = begin main_address = home_address if prefers_home_address? main_address ||= work_address main_address ||= addresses.first # some semi-sensible default end end end দুর্ভাগ্যক্রমে, এট কিছু বদশ見える, কিন্তু nil, false এবং সবকিছু হ্যান্ডেল করে। (নাল কেস হ্যান্ডেল করতে Null Objects এবং খালি অ্যারে ব্যবহার করেও এই সমস্যা এড়ানো যায়। nil এড়াতে আরেকটা কারণ!)
প্যারামিটার হলে কী?
সিম্পল অ্যাকসেসরের জন্য মেমোইজেশন প্যাটার্ন আছে। তবে প্যারামিটার লেবার মেথড মেমোইজ করতে চাইলে?
class City < ActiveRecord::Base def self.top_cities(order_by) where(top_city: true).order(order_by).to_a end end রুবির Hash-র একটি ইনিশিয়ালাইজার আছে যা এই পরিস্থিতির জন্য পারফেক্টলি কাজ করে। Hash.new ব্লক দিয়ে কল করতে পারেন:
Hash.new {|h, key| h[key] = some_calculated_value } তখন যখনই হ্যাশে কোনো সেট না করা কী এক্সেস করতে যাবেন, ব্লকটি এক্সিকিউট হবে। আর হ্যাশটিই এবং এক্সেস করা কী ব্লকে পাস হবে।
তাই এই মেথড মেমোইজ করতে চাইলে:
class City < ActiveRecord::Base def self.top_cities(order_by) @top_cities ||= Hash.new do |h, key| h[key] = where(top_city: true).order(key).to_a end @top_cities[order_by] end end আপনি order_by-তে যা পাস করবেন, সঠিক ফলাফল মেমোইজ হবে। ব্লকটি তभी কল হয় যখন কী এক্সিস্ট করে না, তাই ব্লকের ফলাফল nil বা false হলে চিন্তা নেই।
আশ্চর্য, Hash অ্যারে কীও হ্যান্ডেল করে:
h = {} h[["a", "b"]] = "c" h[["a", "b"]] # => "c" তাই যেকোনো সংখ্যক প্যারামিটার আছে সেগুলোতে এই প্যাটার্ন ব্যবহার করতে পারবেন!
এত কষ্ট কেন?
অনেক মেথডে এই প্যাটার্ন যোগ করলে কোড খুব অরিডেবল হয়ে যাবে। মেথডগুলো শুধু সেরেমনি হবে, সাবস্ট্যান্স নেই।
তাই যদি অ্যাপে অনেক মেমোইজেশন দরকার হয়, তবে একটি জেম ব্যবহার করুন যেটা ফ্রেন্ডলি API দিয়ে মেমোইজেশন হ্যান্ডেল করে। Memoist ভালো একটি অপশন, আর রেইলসের পুরনো মডিউলের মতো। (বা আপনার নতুন মেমোইজেশন জ্ঞান দিয়ে নিজে একটা বানাতে পারেন)।
তবুও, এইরকম প্যাটার্ন এক্সপ্লোর করা সবসময় আকর্ষণীয়, কীভাবে বানানো হয়, কোথায় কাজ করে, কোথায় শার্প এজ আছে। এবং রুবির কিছু কম পরিচিত ফিচার সম্পর্কে মজার জিনিস শিখতে পারবেন।