রুবির ক্লাস ভেরিয়েবল বিভ্রান্তিকর। এক্সপার্ট রুবি ইউজারও কখনও কখনও সেগুলো বুঝতে কষ্ট পান। সবচেয়ে স্পষ্ট উদাহরণ ইনহেরিটেন্সের সাথে জড়িত:
class Fruit
@@kind = nil
def self.kind
@@kind
end
end
class Apple < Fruit
@@kind = "apple"
end
Apple.kind # => "apple"
Fruit.kind # => "apple"
kind ভেরিয়েবল চাইল্ড ক্লাসে পরিবর্তন করলে পেরেন্ট ক্লাসেও পরিবর্তন হয়ে যায়। এটি খুব গোলমাল। কিন্তু এটিই ভাষার নিয়ম। এটি অনেক আগে একটি ডিজাইন সিদ্ধান্ত ছিল Smalltalk অনুকরণের জন্য।
এটি আরও খারাপ
ক্লাস ভেরিয়েবলের অন্যান্য উদাহরণ আছে যা আর্কিটেকচারাল চয়েস না হয়ে ইমপ্লিমেন্টেশন কুইর্কস মনে হয়। আজ আমি 그중 একটি নিয়ে আলোচনা করব যা আমার মতে আকর্ষণীয়।
আমরা দুটি কোডের টুকরো তুলনা করব। সেগুলো দেখতে একই ফলাফল দেবে বলে লাগে কিন্তু দেয় না।
প্রথম উদাহরণে আমরা একটি ক্লাস ভেরিয়েবল সেট করি এবং সেটি রিটার্ন করার একটি মেথড তৈরি করি। এখানে কোনো ফ্যান্সি কিছু নেই। এবং সব কিছু প্রত্যাশা অনুযায়ী কাজ করে।
class Foo
@@val = 1234
# ক্লাস মেথড ডিক্লেয়ার করার শর্টহ্যান্ড।
class << self
def val
@@val
end
end
end
Foo.val # => 1234
হয়তো আপনি জানতেন না, class << self ক্লাস ডেফিনিশনের ভেতরে থাকতে হয় না। নিচের উদাহরণে আমরা এটিকে বাইরে নিয়ে এলাম। একটি ক্লাস মেথড যোগ হয়েছে কিন্তু সেটি ক্লাস ভেরিয়েবল অ্যাক্সেস করতে পারছে না।
class Bar
@@val = 1234
end
class << Bar
def val
@@val
end
end
Bar.val
# warning: class variable access from toplevel
# NameError: uninitialized class variable @@val in Object
যখন আমরা আমাদের ফাংশন থেকে ক্লাস ভেরিয়েবল অ্যাক্সেস করতে চাই, একটি ওয়ার্নিং এবং এক্সসেপশন পাই। কী হচ্ছে?
লেক্সিক্যাল স্কোপ প্রবেশ
আমি আরো বেশি নিশ্চিত হচ্ছি যে লেক্সিক্যাল স্কোপ রুবির ৯৯% विचিত্র এবং বিভ্রান্তিকর দিকের উৎস।
যদি আপনি টার্মটি জানতে না থাকেন, লেক্সিক্যাল স্কোপিং বলতে বোঝায় জিনিসগুলো কোডে কোথায় দেখায় তার উপর ভিত্তি করে গ্রুপ করা, নাকি তারা একটি অ্যাবস্ট্রাক্ট অবজেক্ট মডেলে কোথায় বেলং করে তার উপর ভিত্তি করে। একটি উদাহরণ দেখানো সহজ:
class B
# x এবং y একই লেক্সিক্যাল স্কোপ শেয়ার করে
x = 1
y = 1
end
class B
# z-এর লেক্সিক্যাল স্কোপ x এবং y থেকে ভিন্ন, যদিও একই ক্লাসে।
z = 3
end
ক্লাস লেক্সিক্যালি নির্ধারিত
তাহলে আমাদের ক্লাস ভেরিয়েবল উদাহরণে লেক্সিক্যাল স্কোপ কীভাবে কাজ করছে?
এক্সসেপশন রescue করার সময় রুবিকে জানতে হয় কোন ক্লাস থেকে ভেরিয়েবলটি নেওয়া হবে। এটি লেক্সিক্যাল স্কোপিং ব্যবহার করে ক্লাসটি খুঁজে বের করে।
যদি আমরা কাজ করছে উদাহরণটি নিকট থেকে দেখি, তবে যে কোডটি ক্লাস ভেরিয়েবল অ্যাক্সেস করছে সেটি ফিজিক্যালি ক্লাস ডেফিনিশনের ভেতরে।
class Foo
class << self
def val
# আমি Foo-র লেক্সিক্যালি স্কোপড!
@@val
end
end
end
নন-ওয়ার্কিং উদাহরণে, ক্লাস ভেরিয়েবল অ্যাক্সেস করার কোডটি ক্লাসের সাথে লেক্সিক্যালি স্কোপড নয়।
class << Bar
def val
# Foo কোথাও নেই।
@@val
end
end
কিন্তু যদি এটি ক্লাসের সাথে লেক্সিক্যালি স্কোপড নয়, তবে কিসের সাথে স্কোপড? রুবি যে ওয়ার্নিং প্রিন্ট করে সেটি আমাদের একটি সংকেত দেয়: warning: class variable access from toplevel।
নন-ওয়ার্কিং উদাহরণে আসলে ক্লাস ভেরিয়েবলটি টপ-লেভেল অবজেক্টের সাথে লেক্সিক্যালি স্কোপড। এটি কিছু খুব অসামান্য আচরণ করতে পারে।
উদাহরণস্বরূপ, যদি আমরা টপ-লেভেল অবজেক্টের লেক্সিক্যাল স্কোপ থেকে ক্লাস ভেরিয়েবল সেট করার চেষ্টা করি, তবে ক্লাস ভেরিয়েবলটি Object-এ সেট হয়ে যায়।
class Bar
end
class << Bar
def val=(n)
# এই কোড টপ-লেভেল অবজেক্টের সাথে লেক্সিক্যালি স্কোপড।
# অর্থাৎ, `@@val` `Object`-এ সেট হবে, `Bar`-এ নয়
@@val = n
end
end
Bar.val = 100
# Whaa?
Object.class_variables # => [:@@foo]
আরও উদাহরণ
ক্লাসের লেক্সিক্যাল স্কোপের বাইরে থেকে ক্লাস ভেরিয়েবল রেফারেন্স করার অনেক উপায় আছে। সবই আপনাকে সমস্যা দেবে।
আপনার আনন্দের জন্য কিছু উদাহরণ:
class Foo
@@foo = :foo
end
# এটি কাজ করবে না
Foo.class_eval { puts @@foo }
# এটিও কাজ করবে না
Foo.send :define_method, :x do
puts @@foo
end
# ..এবং আপনি মডিউল সম্পর্কে ভাবছিলেন কি?
module Printable
def foo
puts @@foo
end
end
class Foo
@@foo = :foo
include Printable
end
Foo.new.foo
এখন আপনি জানেন কেন সবাই বলে রুবিতে ক্লাস ভেরিয়েবল কখনো ব্যবহার করবেন না। :)