আজকাল রুবি কোডে #freeze ব্যবহার দেখা খুব সাধারণ। কিন্তু এটি কেন ব্যবহার করা হচ্ছে তা প্রায়শই সম্পূর্ণ স্পষ্ট নয়। এই পোস্টে আমরা ডেভেলপাররা ভেরিয়েবল ফ্রিজ করার সবচেয়ে সাধারণ কারণগুলো দেখব। প্রতিটি কারণ ব্যাখ্যা করতে, আমি রেলস কোডবেস এবং অন্যান্য জনপ্রিয় ওপেন-সোর্স প্রজেক্ট থেকে উদাহরণ কোড সংকলন করেছি।
ইমিউটেবল কনস্ট্যান্ট তৈরি
রুবিতে, কনস্ট্যান্টগুলো মিউটেবল। এটি কিছুটা বিভ্রান্তিকর, কিন্তু কোড বোঝা সহজ। এখানে, আমি একটি স্ট্রিং কনস্ট্যান্ট তৈরি করেছি এবং সেটিতে অন্য একটি স্ট্রিং অ্যাপেন্ড করছি।
MY_CONSTANT = "foo"
MY_CONSTANT << "bar"
puts MY_CONSTANT.inspect # => "foobar"
#freeze ব্যবহার করে, আমি একটি কনস্ট্যান্ট তৈরি করতে পারি যা সত্যিই কনস্ট্যান্ট। এইবার, যখন আমি স্ট্রিং মডিফাই করার চেষ্টা করি, তখন আমি একটি RuntimeError পাই।
MY_CONSTANT = "foo".freeze
MY_CONSTANT << "bar" # => RuntimeError: can't modify frozen string
এখানে ActionDispatch কোডবেস থেকে একটি রিয়েল-ওয়ার্ল্ড উদাহরণ। রেলস লগে সংবেদনশীল ডেটা "[FILTERED]" টেক্সট দিয়ে রিপ্লেস করে লুকিয়ে রাখে। এই টেক্সটটি একটি ফ্রোজেন কনস্ট্যান্টে সংরক্ষিত থাকে।
module ActionDispatch
module Http
class ParameterFilter
FILTERED = '[FILTERED]'.freeze
...
অবজেক্ট অ্যালোকেশন কমানো
আপনার রুবি অ্যাপ গতিবর্ধন করার জন্য আপনি যা সেরা কাজ করতে পারেন তা হলো তৈরি হওয়া অবজেক্টের সংখ্যা কমানো। অধিকাংশ অ্যাপে ছড়িয়ে থাকা স্ট্রিং লিটারেল থেকে অবজেক্ট অ্যালোকেশনের একটি বিরক্তিকর উৎস পাওয়া যায়।
প্রতিবার আপনি log("foobar") এর মত একটি মেথড কল করলে, আপনি একটি নতুন String অবজেক্ট তৈরি করেন। যদি আপনার কোড প্রতি সেকেন্ডে হাজার হাজার বার এমন মেথড কল করে, তবে আপনি প্রতি সেকেন্ডে হাজার হাজার স্ট্রিং তৈরি (এবং গার্বেজ-কলেক্ট) করছেন। এটা অনেক ওভারহেড!
সৌভাগ্য سے, রুবি আমাদের একটি উপায় দেয়। যদি আমরা স্ট্রিং লিটারেলগুলো ফ্রিজ করি, তবে রুবি ইন্টারপ্রেটার শুধু একটি String অবজেক্ট তৈরি করবে এবং ভবিষ্যতের ব্যবহারের জন্য তা ক্যাশ করে রাখবে। আমি ফ্রোজেন বনাম নন-ফ্রোজেন স্ট্রিং আর্গুমেন্টের পারফরম্যান্স দেখানোর জন্য একটি কুইক বেঞ্চмар্ক তৈরি করেছি। এটা প্রায় ৫০% পারফরম্যান্স বৃদ্ধি দেখায়।
require 'benchmark/ips'
def noop(arg) end
Benchmark.ips do |x|
x.report("normal") { noop("foo") }
x.report("frozen") { noop("foo".freeze) }
end
# MRI 2.2.2-র ফলাফল:
# Calculating -------------------------------------
# normal 152.123k i/100ms
# frozen 167.474k i/100ms
# -------------------------------------------------
# normal 6.158M (± 3.3%) i/s - 30.881M
# frozen 9.312M (± 3.5%) i/s - 46.558M
রেলস রাউটারে এটি কাজে লাগানো দেখতে পারেন। যেহেতু রাউটার প্রতিটি ওয়েব পেজ রিকোয়ারেন্টে ব্যবহৃত হয়, তাই এটি দ্রুত হতে হবে। এর মানে হলো অনেক ফ্রোজেন স্ট্রিং লিটারেল।
# excerpted from https://github.com/rails/rails/blob/f91439d848b305a9d8f83c10905e5012180ffa28/actionpack/lib/action_dispatch/journey/router/utils.rb#L15
def self.normalize_path(path)
path = "/#{path}"
path.squeeze!('/'.freeze)
path.sub!(%r{/+\Z}, ''.freeze)
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
path = '/' if path == ''.freeze
path
end
রুবি ২.২+ এ বিল্ট-ইন অপ্টিমাইজেশন
রুবি ২.২ এবং পরবর্তী ভার্সনগুলো (MRI) স্বয়ংক্রিয়ভাবে স্ট্রিং লিটারেল ফ্রিজ করে দেয় যখন সেগুলো হ্যাশ কি হিসেবে ব্যবহৃত হয়।
user = {"name" => "george"}
# রুবি ২.২+ এ
user["name"]
# ...এটি সমতুল্য, রুবি ২.১ বা তার নিচে
user["name".freeze]
এবং ম্যাটজ অনুযায়ী, রুবি ৩-এ সব স্ট্রিং লিটারেল ডিফল্টরূপে ফ্রিজ হয়ে যাবে।
link: DevelopersMeeting20150820Japan - String literals are frozen (immutable) by default in Ruby 3.0
https://t.co/4XcelftmSa
— Yukihiro Matsumoto (@yukihiro_matz) August 20, 2015
ভ্যালু অবজেক্ট এবং ফাংশনাল প্রোগ্রামিং
যদিও রুবি একটি ফাংশনাল প্রোগ্রামিং ভাষা নয়, অনেক রুবিস্টরা ফাংশনাল স্টাইলে কাজ করার মূল্য বুঝতে শুরু করেছেন। এই স্টাইলের প্রধান নীতিগুলোর একটি হলো সাইড ইফেক্ট এড়ানো। অবজেক্টগুলো ইнициয়ালাইজেশনের পর কখনো পরিবর্তন করা উচিত নয়।
কনস্ট্রাক্টরের ভিতরে freeze মেথড কল করে, একটি অবজেক্ট কখনো পরিবর্তন হবে না তা গ্যারান্টি করা সম্ভব। যেকোনো অনিচ্ছাকৃত সাইড-ইফেক্ট একটি এক্সেপশন ফেলাবে।
class Point
attr_accessor :x, :y
def initialize(x, y)
@x = x
@y = y
freeze
end
def change
@x = 3
end
end
point = Point.new(1,2)
point.change # RuntimeError: can't modify frozen Point