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

রুবি ৩-এর নতুন টাইপিং ল্যাঙ্গুয়েজ RBS পরিচিতি: শক্তিশালী কোড রিলায়াবিলিটির জন্য

চির প্রতীক্ষিত রুবি ভার্সন ৩.০.০ এন্ডে রিলিজ হয়েছে। অন্যান্য অনেক চমৎকার উন্নতির পাশাপাশি, যেমন গত ভার্সনের চেয়ে ৩x ফাস্টার পারফরম্যান্স বוסט, কনকারেন্সি-প্যারালেল এক্সপেরিমেন্টাল ফিচার ইত্যাদি, রুবি টিম একটি নতুন সিন্ট্যাক্স ল্যাঙ্গুয়েজ পরিচয় করিয়েছে রুবিতে ডাইনামিক টাইপিংর জন্য: RBS।

এটা এমন কিছু যা টিম سالها থেকে আলোচনা করছিল, কমিউনিটি-ডেভেলপড টুলস যেমন Sorbet-এর স্ট্যাটিক টাইপ চেকিং-এ সাফল্যের ভিত্তিতে।

Sorbet একটি পাওয়ারফুল টাইপ চেকার যেটাকে Stripe ব্যাক করে। এটি আপনার কোড চেক করে আনোটেশন এবং/অথবা RBI ফাইল ডিফাইন করে। RBI ফাইলগুলো, বদলে, স্ট্যাটিক এবং ডাইনামিক কম্পোনেন্টের মাঝে ইন্টারফেস হিসেবে কাজ করে তাদের "বর্ণনা" দিয়ে (কনস্ট্যান্ট, অ্যান্সেস্টর, মেটাপ্রোগ্রামিং কোড, এবং আরও অনেক কিছু)।

তাহলে, যদি Sorbet অধিকাংশ স্ট্যাটিক চেকিং-এ ডেল করে এবং RBS ডাইনামিক টাইপিং এড্রেস করার জন্য তৈরি হয়েছে, তবে তাদের মাঝে পার্থক্য কী? সেগুলো কীভাবে কো-একজিস্ট করবে? আমি কখন কোনটি ব্যবহার করব?

এই খুবই কমন প্রশ্ন RBS-এর মেজর রোল নিয়ে। সেজন্য আমরা এই পিস লিখতে সিদ্ধান্ত নিলাম। প্র্যাকটিক্যালি, কেন আপনাকে এটা আডপ্ট করার কথা ভাববেন তার ওপর ভিত্তি করে ক্ল্যারিফাই করতে। চলুন সরাসরি ডুব মারি!

বেসিকস দিয়ে শুরু (Starting with the Basics)

স্ট্যাটিক টাইপিং এবং ডাইনামিক টাইপিং-এর মাঝের পার্থক্য সম্পর্কে একটি স্পষ্ট বোঝাপড়া দিয়ে শুরু করি। যদিও এটা বেসিক, কিন্তু RBS-এর রোল বুঝতে এটি একটি কী কনসেপ্ট।

একটি স্ট্যাটিক্যালি টাইপড ল্যাঙ্গুয়েজ থেকে একটি কোড স্নিপেট রেফারেন্স হিসেবে নিই:

➜ String str = ""; str = 2.4;

এই ধরণের ল্যাঙ্গুয়েজ তার অবজেক্ট এবং ভেরিয়েবলের টাইপের জন্য চিন্তিত হয় তা কোনো নতুন কথা নয়। তাহলে, উপরের কোডটি একটি এরর থ্রো করবে।

রুবি, অন্যান্য অনেক ল্যাঙ্গুয়েজের মতো যেমন JavaScript, Python, এবং Objective-C, আপনার অবজেক্টের জন্য আপনি কোন টাইপ টার্গেট করছেন সেটার ওপর তত ফোকাস করে না।

রুবিতে একই কোডটি সফলভাবে রান করবে, নিচে দেখানো হলো:

➜ irb str = "" str = 2.4 puts str # prints 2.4

এটা সম্ভব হয় কারণ রুবির ইন্টারপ্রেটার জানে কীভাবে ডাইনামিক্যালি এক টাইপ থেকে অন্য টাইপে স্যুইচ করতে হয়।

তবে, ইন্টারপ্রেটার যা অনুমতি দেয় সেটার একটি লিমিট রয়েছে। উদাহরণস্বরূপ, নিচের কোড চেঞ্জ নিন:

➜ irb val = "6.0" result = val + 2.0 puts result

এটি নিচের এরর প্রডিউস করবে:

Error: no implicit conversion of Float into String

একই কোড JavaScript-এ রান করলে, উদাহরণস্বরূপ, ঠিকঠাক রান করবে।

শিক্ষা: রুবি ডাইনামিক্যালি টাইপ ইনফার করে, অবশ্য; কিন্তু অন্য মেজর ডাইনামিক ল্যাঙ্গুয়েজের বিপরীতে, এটি সবকিছু এক্সেপ্ট করে না। সেটা সম্পর্কে অবগত रहুন!

এটাই সেটা যেখানে টাইপ চেকারগুলো (চाहে স্ট্যাটিক হোক বা ডাইনামিক) উপকারী হয়।

RBS vs Sorbet

ঠিক আছে, ডাইনামিক vs স্ট্যাটিক বিষয়ে আপনার পয়েন্ট বুঝলাম। কিন্তু Sorbet সম্পর্কে কী? সেটা কী ডিপ্রিকেটেড হবে?

সর্বথা নয়। RBS এবং Sorbet-এর মাঝে প্রাইমারি (এবং হয়তো সবচোয়ে গুরুত্বপূর্ণ) পার্থক্য হলো: 前者 শুধু একটি ল্যাঙ্গুয়েজ, যখন পরে একটি কমপ্লিট টাইপ চেকার নিজে।

রুবি টিম RBS-এর মূল লক্ষ্য দাবি করে: আপনার কোডের স্ট্রাকচার বর্ণনা করা। এটি টাইপ চেকিং পারফর্ম করবে না, বরং স্ট্রাকচার ডিফাইন করবে যেটা টাইপ চেকাররা (Sorbet বা অন্য যেকোনো) ব্যবহার করতে পারবে, তো, টাইপ চেক করার জন্য। কোড স্ট্রাকচার একটি নিউ ফাইল এক্সটেনশনে সংরক্ষিত থাকে — .rbs।

চেক আউট করতে, নিচের রুবি ক্লাস নিই উদাহরণ হিসেবে:

class Super def initialize(val) @val = val end def val? @val end end class Test < Super def initialize(val, flag) super(val) @flag = flag end def flag? @flag end end

এটি রুবিতে একটি সিম্পল ইনহেরিটেন্স রিপ্রেজেন্ট করে। নোট করার إن্টারেস্টিং বিষয়: ক্লাসে ব্যবহৃত প্রতিটি অ্যাট্রিবিউটের টাইপ আমরা অনুমান করি না পারি, flag-এর ব্যতিরেক্ত।

যেহেতু flag একটি ডিফল্ট ভ্যালু দিয়ে ইনিশিয়ালাইজ হয়েছে, ডেভেলপার এবং একটি টাইপ চেকার উভয়ই টাইপ ইনফার করতে পারে সেটা আরও মিসইউজ রোধ করার জন্য।

উপরের ক্লাসের RBS ফরম্যাটে একটি প্রপার রিপ্রেজেন্টেশন হবে নিচের মত:

class Super attr_reader val : untyped def initialize : (val: untyped) -> void end class Test < Super attr_reader flag : bool def initialize : (val: untyped, ?flag: bool) -> void def flag? : () -> bool end

এটাকে পচাতে কিছু সময় নিন। এটা একটি ডিক্লেরেশন ল্যাঙ্গুয়েজ, তাই শুধু সিগ্নেচারগুলোই RBS ফাইলে দৃষ্টিগোচর হতে পারে। সিম্পল, তো?

এটা অটো-জেনারেটেড হোক CLI টুল দিয়ে (আরো পরে সেটা) বা আপনার দিয়ে, একটি টাইপকে untyped হিসেবে আনোটেট করা সেফার যখন সেটা অনুমান করা যাবে না।

আপনি যদি val-এর টাইপ নিয়ে নিশ্চিত হন, উদাহরণস্বরূপ, আপনার RBS ম্যাপিং নিচের মত স্যুইচ করা যাবে:

class Super attr_reader val : Integer def initialize : (val: Integer) -> void end

এছাড়া নোট করুন: RBS এবং RBI ফাইলগুলোর ইন্টারঅপারেবিলিটি এখনও ডেভেলপমেন্ট আন্ডার। লক্ষ্য হলো: Sorbet এবং অন্যান্য চেকার টুলসকে একটি অফিসিয়াল এবং সেন্ট্রালাইজড বেসিস দেয়া যাতে সেগুলো ফলো করতে পারে।

RBS CLI টুল (The RBS CLI Tool)

RBS ডেভেলপ করার সময় রুবি টিমের একটি গুরুত্বপূর্ণ কনসিডারেশন ছিল: একটি CLI টুল শিপ করা যা ডেভেলপারদের এটা ট্রাই আউট করতে এবং কীভাবে ব্যবহার করবেন সেটা শিখতে সাহায্য করবে। এটাকে rbs বলা হয় এবং রুবি ৩-এ ডিফল্টলি আসে। আপনি যদি এখনো আপনার রুবি ভার্সন আপগ্রেড করেননি, তবে আপনার প্রজেক্টে সরাসরি এর জেম যোগ করতে পারেন:

➜ gem install rbs 

rbs help কমান্ড কমান্ড ইউজেজ এবং অবেলেবল কমান্ডগুলো দেখাবে।

উপলব্ধ কমান্ডের তালিকা

এই কমান্ডের অধিকাংশ ফোকাস করে রুবি কোড স্ট্রাকচার পার্সিং এবং অ্যানালাইসিস করার ওপর। উদাহরণস্বরূপ, ancestors কমান্ড একটি দেয়া ক্লাসের হায়ারার্কিক্যাল স্ট্রাকচার সুইপ করে এর অ্যান্সেস্টার চেক করার জন্য:

➜ rbs ancestors ::String ::String ::Comparable ::Object ::Kernel ::BasicObject

methods কমান্ড একটি দেয়া ক্লাসের সকল মেথড স্ট্রাকচার ডিসপ্লে করে:

➜ rbs methods ::String ! (public) != (public) !~ (public) ... Array (private) Complex (private) Float (private) ... autoload? (private) b (public) between? (public) ...

একটি স্পেসিফিক মেথড স্ট্রাকচার দেখতে চান? method যান:

➜ rbs method ::String split ::String#split defined_in: ::String implementation: ::String accessibility: public types: (?::Regexp | ::string pattern, ?::int limit) -> ::Array[::String] | (?::Regexp | ::string pattern, ?::int limit) { (::String) -> void } -> self

RBS-এ আজ থেকে শুরু করছেন যারা, prototype কমান্ড ক্লাসের জন্য টাইপ স্ক্যাফোল্ড করতে খুব সাহায্য করতে পারে যেগুলো ইতিমধ্যে এক্সিস্ট করে। কমান্ডটি RBS ফাইলগুলোর প্রোটোটাইপ জেনারেট করে।

পূর্বের Test < Super ইনহেরিটেন্স উদাহরণ নিই এবং কোডটি appsignal.rb নামে একটি ফাইলে সেভ করি। তারপর নিচের কমান্ড রান করুন:

➜ rbs prototype rb appsignal.rb 

যেহেতু কমান্ডটি rb, rbi, এবং runtime জেনারেটর সাপোর্ট করে, আপনাকে prototype কমান্ডের ঠিক পরে আপনি যে ফাইল টাইপ স্ক্যাফোল্ড করছেন সেটা স্পেসিফাই করতে হবে, তারপর ফাইল পাথনেম।

এক্সিকিউশনের রেজাল্ট নিচে:

class Super def initialize: (untyped val) -> untyped def val?: () -> untyped end class Test < Super def initialize: (untyped val, ?flag: bool flag) -> untyped def flag?: () -> untyped end

আমাদের প্রথম RBS ভার্সনের সাথে খুব সিমিলার। আগে বলা মতে, টুল সেগুলো untyped হিসেবে মার্ক করে যেসব টাইপ অনুমান করা যাবে না।

এটা মেথড রিটার্নের জন্যও কাউন্ট করে। flag ডেফিনিশনের রিটার্ন টাইপ লক্ষ্য করুন। একজন ডেভেলপার হিসেবে, আপনি সম্ভবত নিশ্চিত যে মেথডটি সবসময় একটি বুলিয়ান রিটার্ন করে, কিন্তু রুবির ডাইনামিক নেচারের কারণে, টুল ১০০% বলতে পারছে না যে সেটা আসলেই সেটা।

এখানেই রুবি ৩-এর আরেকটি সন্তান রক্ষায় আসে: TypeProf।

TypeProf টুল (The TypeProf Tool)

TypeProf হলো রুবির জন্য একটি টাইপ অ্যানালাইসিস টুল যা কিছু সিন্ট্যাক্স ট্রি ইন্টারপ্রিটেশনের ওপর তৈরি হয়েছে।

এখনও এক্সপেরিমেন্টল থাকেও, এটি কোড কী করার চেষ্টা করছে সেটা বুঝতে খুব পাওয়ারফুল প্রমাণিত হয়েছে।

আপনার কাছে যদি রুবি ৩ না থাকে, তবে সিম্পলি আপনার প্রজেক্টে জেম যোগ করুন:

➜ gem install typeprof 

এখন, একই appsignal.rb ফাইল টা TypeProf-এর বিরুদ্ধে রান করুন:

➜ typeprof appsignal.rb 

এটি আউটপুট:

# Classes class Super @val: untyped def initialize: (untyped) -> untyped def val?: -> untyped end class Test < Super @val: untyped @flag: true def initialize: (untyped, ?flag: true) -> true def flag?: -> true end

নোট করুন flag কীভাবে এখন ম্যাপ হয়েছে। এটি শুধুমাত্র সম্ভব হয়েছে কারণ, RBS প্রোটোটাইপ যা করে তার বিপরীতে, TypeProf মেথডের বডি স্ক্যান করে বুঝতে চায় সেই স্পেসিফিক ভেরিয়েবলের ওপর কী অ্যাকশন পারফর্ম হচ্ছে। যেহেতু সেটা এই ভেরিয়েবলে কোনো ডাইরেক্ট চেঞ্জ শনাক্ত করতে পারলো না, TypeProf সফলভাবে মেথড রিটার্নকে বুলিয়ান হিসেবে ম্যাপ করলো।

উদাহরণস্বরূপ, কল্পনা করুন TypeProf-এর কাছে অন্যান্য ক্লাসগুলো এক্সেস থাকে যেগুলো Test ক্লাস ইনস্ট্যানশিয়েট এবং ব্যবহার করে। সেটার সাথে, এটি আপনার কোডে আরও গভীরে যেতে পারে এবং এর প্রিডিকশন ফাইন-টিউন করতে পারে। মনে করুন নিচের কোড স্নিপেট appsignal.rb ফাইলের শেষে যোগ করা হয়েছে:

testSub = Test.new("My value", "My value" == "") testSup = Super.new("My value")

এবং আপনি initialize মেথড সিগনেচার নিচের মত চেঞ্জ করেছেন:

def initialize(val, flag)

কমান্ড রি-রান করলে, এটি আউটপুট হওয়া উচিত:

# Classes class Super @val: String def initialize: (String) -> String def val?: -> String end class Test < Super @val: String @flag: bool def initialize: (String val, bool flag) -> bool def flag?: -> bool end

সুপার কুল!

TypeProf ইনহেরিটেড অ্যাট্রিবিউটের সাথে খুব ভালো ডিল করে না। সেজন্য আমরা একটি নতুন Super অবজেক্ট ইনস্ট্যানশিয়েট করলাম। অন্যথায়, এটা বুঝতে পারতো না যে val একটি String।

TypeProf-এর প্রধান প্রো: এর সেফটি। যেকোনো সময় এটি নিশ্চিতভাবে কিছু ফিগার আউট করতে না পারলে, untyped রিটার্ন করবে।

পার্শিয়াল RBS স্পেসিফিকেশন (Partial RBS Specification)

অফিশিয়াল ডক্স থেকে একটি গুরুত্বপূর্ণ সতর্কতা: যদিও TypeProf খুব পাওয়ারফুল, আপনাকে এর লিমিটেশন সম্পর্কে অবগত থাকা উচিত RBS কোড কী জেনারেট করতে পারে এবং কী না সেটা নিয়ে।

উদাহরণস্বরূপ, রুবি ডেভেলপারদের মধ্যে একটি কমন প্র্যাকটিস হলো মেথড ওভারলোডিং যেখানে আপনি একটি মেথডের ভিন্ন বিহেভিয়ার ইনভোক করেন এর আর্গুমেন্টের ওপর নির্ভর করে।

কল্পনা করুন Super ক্লাসে একটি নতুন মেথড spell যোগ করা হয়েছে, যা একটি Integer বা একটি String রিটার্ন করে প্যারামিটার টাইপের ওপর ভিত্তি করে:

def spell(val) if val.is_a?(String) "" else 0 end end

RBS এই প্র্যাকটিকে অ্যাডপ্ট করে ইউনিয়ন টাইপ (একটি ভ্যালু যা একাধিক পসিবল টাইপ রিপ্রেজেন্ট করে) সিন্ট্যাক্স দিয়ে ওভারলোডিং ডিল করার অনুমতি দিয়ে:

def spell: (String) -> String | (Integer) -> Integer

TypeProf শুধু মেথড বডি অ্যানালাইসিস করে এই ইনফার করতে পারে না। এটাকে সাহায্য করতে, আপনি ম্যানুয়ালি এমন একটি ডেফিনিশন আপনার RBS ফাইলে যোগ করতে পারেন এবং TypeProf সবসময় সেখানে প্রথমে ইনস্ট্রাকশন চেক করবে।

এই জন্য, আপনাকে কমান্ডের শেষে RBS ফাইল পাথ যোগ করতে হবে:

typeprof appsignal.rb appsignal.rbs

নিচে নিউ আউটপুট দেখুন:

class Super ... def spell: (untyped val) -> (Integer | String) end

পাশাপাশি, আমরা রানটাইমে রিয়েল টাইপগুলো ভেরিফাই করতে পারি Kernel#p দিয়ে টেস্ট করার জন্য ওভারলোডিং কাজ করছে কিনা, appsignal.rb ফাইলের শেষে নেক্সট দুটি লাইন যোগ করে:

p testSup.spell(42) p testSup.spell("str")

এটি আউটপুট হওয়া উচিত:

# Revealed types # appsignal.rb:11 #=> Integer # appsignal.rb:12 #=> String ...

আরো ইনফরমেশনের জন্য, বিশেষ করে TypeProf লিমিটেশন কনসার্নিং সেকশন, অফিশিয়াল ডক্স রেফার করুন।

ডাক টাইপিং (Duck Typing)

আপনি এটা আগে শুনেছেন। যদি একটি রুবি অবজেক্ট সকল কাঝা যা যা করে যা একটি হাঁস করে, তবে সেটা একটি হাঁস।

যেমন আমরা দেখেছি, রুবি আপনার অবজেক্টগুলো কী হওয়া উচিত সেটার কെയার করে না। টাইপগুলো ডাইনামিক্যালি চেঞ্জ হতে পারে এবং অবজেক্ট রেফারেন্সও।

যদিও হেল্পফুল, ডাক টাইপিং ট্রিকি হতে পারে। একটি উদাহরণ দেখি।

মান লেণ, আজ থেকে, Super ক্লাসের জন্য আপনি যে val অ্যাট্রিবিউট ডিক্লেয়ার করলেন, যা একটি String, সেটা সবসময় ইন্টিজারে কনভার্টেবল হতে হবে।

ডেভেলপাররা সবসময় কনভারশন গ্যারান্টি করবে সেটা বিশ্বাস করার বদলে (হয়তো, অন্যথায় এরর থ্রো করবে), আপনি একটি ইন্টারফেস তৈরি করতে পারেন যা বলে:

interface _IntegerConvertible def to_int: () -> Integer end

ইন্টারফেস টাইপ এক বা একাধিক মেথড প্রদান করে যা কংক্রিট ক্লাস এবং মডিউল থেকে ডিট্যাচড। এইভাবে, আপনি যখন একটি নির্দিষ্ট টাইপ Super ইনস্ট্যানশিয়েশনে পাস করতে চান, আপনি সিম্পলি নিচের মত করতে পারেন:

class Super attr_reader val : _IntegerConvertible def initialize : (val: _IntegerConvertible) -> void end

এই ইন্টারফেস ইমপ্লিমেন্ট করার কংক্রিট ক্লাস বা মডিউলকে প্রপার ভ্যালিডেশন করা নিশ্চিত করতে হবে।

মেটাপ্রোগ্রামিং (Metaprogramming)

হয়তো রুবির সবচোয়ে ডাইনামিক ফিচার হলো: রানটাইমে স elf-কোড তৈরি করার ক্যাপাবিলিটি। সেটাই মেটাপ্রোগ্রামিং।

জিনিসগুলোর অনিশ্চিত নেচারের কারণে, RBS CLI টুল মেটাপ্রোগ্রামিং কোড থেকে RBS জেনারেট করতে পারে না।

নিচের স্নিপেট নিই উদাহরণ হিসেবে:

class Test define_method :multiply do |*args| args.inject(1, :*) end end p Test.new.multiply(2, 3, 5)

এই ক্লাস রানটাইমে multiply নামে একটি মেথড ডিফাইন করে এবং আর্গুমেন্টগুলো ইনজেক্ট করে এবং প্রতিটাকে পূর্বের রেজাল্টের সাথে গুন করতে ইনস্ট্রাক্ট করে।

একবার RBS prototype কমান্ড রান করলে, এটি আউটপুট হওয়া উচিত:

class Test end

আপনার মেটাপ্রোগ্রামিং কোডের কমপ্লেক্সিটির ওপর নির্ভর করে, TypeProf এখনও তার সেরা দেবে কিছু এক্সট্রাক্ট করার জন্য। কিন্তু এটি সবসময় গ্যারান্টিড নয়।

মনে রাখবেন, আপনি সবসময় আপনার নিজস্ব টাইপ ম্যাপিং আপনার RBS ফাইলে যোগ করতে পারেন এবং TypeProf সেগুলো এডভান্সে মানবে। এটাও মেটাপ্রোগ্রামিংর জন্য বৈধ।

এছাড়া, লেটেস্ট রিপোজিটরি চেঞ্জসের সাথে আপডেটেড থাকা গুরুত্বপূর্ণ কারণ টিম কনস্ট্যান্টলি নিউ ফিচার রিলিজ করছে, যাতে মেটাপ্রোগ্রামিং-এ আপডেটও থাকতে পারে।

সেটা বলা থাকলে, আপনার কোডবেসে যদি কোনো ধরনের মেটাপ্রোগ্রামিং থাকে, এই টুলসের সাথে সাবধান থাকুন। এগুলো ব্লাইন্ডলি ব্যবহার করবেন না!

সমেটন (Wrapping Up)

আমরা যা আলোচনা করেছি সেটার পাশাপাশি আরও অনেক ডিটেইল রয়েছে, সেই সাথে RBS এবং TypeProf-এর জন্য এজ ইউজ কেসও যা আপনাকে অবগত থাকা উচিত।

তাই, সেগুলোর জন্য অফিশিয়াল ডক্স রেফার করুন।

RBS এখনও খুব তাজা কিন্তু ইতিমধ্যে Rubyists-এ যারা তাদের কোডবেস অন্যান্য টুলস দিয়ে টাইপ চেক করত, তাদের ওপর বিশাল প্রভাব ফেলেছে।

আপনার কী? আপনি কি ট্রাই আউট করেছেন? RBS-এ আপনার মতামত কী?

P.S. Ruby Magic পোস্টগুলো প্রেস থেকে বের होतेই পেতে চাইলে, আমাদের Ruby Magic নিউজলেটার সাবস্ক্রাইব করুন এবং কখনো একক পোস্ট মিস করবেন না!

  1. স্টফল ভাষার জন্য রুবি ইন্টারপ্রেটার গড়ে তোলা

  2. RBS: রুবির নতুন টাইপ অ্যানোটেশন সিস্টেম ব্যাখ্যা

  3. রুবি ২.৬-এ ৯টি গুরুত্বপূর্ণ উন্নতির অন্বেষণ

  4. কীবোর্ড সমস্যা সমাধান: ভুল কী টাইপ হলে কী করবেন