আসুন রুবিতে প্যাটার্ন ম্যাচিং সম্পর্কে একটি সংক্ষিপ্ত আলোচনা শুরু করি, এটি কী করে এবং কীভাবে এটি কোড পঠনযোগ্যতা উন্নত করতে সাহায্য করতে পারে৷
আপনি যদি কয়েক বছর আগে আমার মতো কিছু হন তবে আপনি এটিকে রেজেক্সে প্যাটার্ন ম্যাচিংয়ের সাথে বিভ্রান্ত করতে পারেন। এমনকি অন্য কোনো প্রসঙ্গ ছাড়াই 'প্যাটার্ন ম্যাচিং'-এর একটি দ্রুত Google অনুসন্ধান আপনাকে সেই সংজ্ঞার কাছাকাছি এমন সামগ্রী নিয়ে আসে৷
আনুষ্ঠানিকভাবে, প্যাটার্ন ম্যাচিং হল অন্য ডেটার বিপরীতে যেকোনো ডেটা (তা অক্ষরের ক্রম, টোকেনের একটি সিরিজ, একটি টিপল বা অন্য কিছু) পরীক্ষা করার প্রক্রিয়া।
প্রোগ্রামিং এর পরিপ্রেক্ষিতে, ভাষার ক্ষমতার উপর নির্ভর করে, এর অর্থ নিম্নলিখিত যেকোনও হতে পারে:
- একটি প্রত্যাশিত ডেটা প্রকারের সাথে মিলে যাওয়া
- একটি প্রত্যাশিত হ্যাশ কাঠামোর সাথে মিল (যেমন নির্দিষ্ট কীগুলির উপস্থিতি)
- একটি প্রত্যাশিত অ্যারের দৈর্ঘ্যের সাথে মিলে যাওয়া
- কিছু ভেরিয়েবলের সাথে মিল (বা তাদের একটি অংশ) বরাদ্দ করা
প্যাটার্ন ম্যাচিংয়ে আমার প্রথম অভিযান ছিল এলিক্সিরের মাধ্যমে। এলিক্সিরের প্যাটার্ন ম্যাচিং এর জন্য প্রথম শ্রেণীর সমর্থন রয়েছে, তাই =
অপারেটর হল, আসলে, match
অপারেটর, সাধারণ অ্যাসাইনমেন্টের পরিবর্তে।
এর মানে হল এলিক্সিরে, নিম্নলিখিতটি আসলে বৈধ কোড:
iex> x = 1
iex> 1 = x
এটি মাথায় রেখে, আসুন রুবি 2.7+ এর জন্য নতুন প্যাটার্ন ম্যাচিং সমর্থন দেখি এবং কীভাবে আমরা এটিকে ব্যবহার করে আমাদের কোডকে আরও পঠনযোগ্য করে তুলতে পারি, আজ থেকে শুরু করে।
case
এর সাথে রুবি প্যাটার্ন ম্যাচিং /in
রুবি একটি বিশেষ case
এর সাথে প্যাটার্ন ম্যাচিং সমর্থন করে /in
অভিব্যক্তি সিনট্যাক্স হল:
case <expression>
in <pattern1>
# ...
in <pattern2>
# ...
else
# ...
end
এটিকে case
এর সাথে বিভ্রান্ত করা উচিত নয় /when
অভিব্যক্তি when
এবং in
শাখাগুলিকে একক case
-এ মিশ্রিত করা যাবে না .
আপনি যদি একটি else
প্রদান না করেন অভিব্যক্তি, কোনো ব্যর্থ মিল একটি NoMatchingPatternError
উত্থাপন করবে .
রুবিতে প্যাটার্ন ম্যাচিং অ্যারে
প্যাটার্ন ম্যাচিং ডেটা প্রকার, দৈর্ঘ্য বা মানগুলির সাথে পূর্ব-প্রয়োজনীয় কাঠামোর সাথে অ্যারেগুলিকে মেলানোর জন্য ব্যবহার করা যেতে পারে৷
উদাহরণ স্বরূপ, নিচের সবগুলোই মিল (উল্লেখ্য যে শুধুমাত্র প্রথম in
case
হিসাবে মূল্যায়ন করা হবে প্রথম ম্যাচের পর দেখা বন্ধ করে দেয়):
case [1, 2, "Three"]
in [Integer, Integer, String]
"matches"
in [1, 2, "Three"]
"matches"
in [Integer, *]
"matches" # because * is a spread operator that matches anything
in [a, *]
"matches" # and the value of the variable a is now 1
end
আপনি যখন একটি মেথড কল থেকে একাধিক সিগন্যাল তৈরি করতে চান তখন এই ধরনের প্যাটার্ন ম্যাচিং ক্লজ খুবই কার্যকর।
এলিক্সির বিশ্বে, এটি প্রায়শই ব্যবহার করা হয় অপারেশন করার সময় যেখানে একটি :ok
উভয়ই থাকতে পারে ফলাফল এবং একটি :error
ফলাফল, উদাহরণস্বরূপ, একটি ডাটাবেসে ঢোকানো।
আরও ভাল পঠনযোগ্যতার জন্য আমরা কীভাবে এটি ব্যবহার করতে পারি তা এখানে:
def create
case save(model_params)
in [:ok, model]
render :json => model
in [:error, errors]
render :json => errors
end
end
# Somewhere in your code, e.g. inside a global helper or your model base class (with a different name).
def save(attrs)
model = Model.new(attrs)
model.save ? [:ok, model] : [:error, model.errors]
end
রুবিতে প্যাটার্ন ম্যাচিং অবজেক্ট
আপনি একটি নির্দিষ্ট কাঠামো প্রয়োগ করতে রুবিতে বস্তুগুলিকেও মেলাতে পারেন:
case {a: 1, b: 2}
in {a: Integer}
"matches" # By default, all object matches are partial
in {a: Integer, **}
"matches" # and is same as {a: Integer}
in {a: a}
"matches" # and the value of variable a is now 1
in {a: Integer => a}
"matches" # and the value of variable a is now 1
in {a: 1, b: b}
"matches" # and the value of variable b is now 2
in {a: Integer, **nil}
"does not match" # This will match only if the object has a and no other keys
end
যেকোনো প্যারামের সাথে মিলের জন্য শক্তিশালী নিয়ম আরোপ করার সময় এটি দুর্দান্ত কাজ করে।
উদাহরণস্বরূপ, যদি আপনি একটি অভিনব অভিবাদন লিখছেন, তবে এটির নিম্নলিখিত (দৃঢ় মতামতযুক্ত) কাঠামো থাকতে পারে:
def greet(hash = {})
case hash
in {greeting: greeting, first_name: first_name, last_name: last_name}
greet(greeting: greeting, name: "#{first_name} #{last_name}")
in {greeting: greeting, name: name}
puts "#{greeting}, #{name}"
in {name: name}
greet(greeting: "Hello", name: name)
in {greeting: greeting}
greet(greeting: greeting, name: "Anonymous")
else
greet(greeting: "Hello", name: "Anonymous")
end
end
greet # Hello, Anonymous
greet(name: "John") # Hello, John
greet(first_name: "John", last_name: "Doe") # Hello, John Doe
greet(greeting: "Bonjour", first_name: "John", last_name: "Doe") # Bonjour, John Doe
greet(greeting: "Bonjour") # Bonjour, Anonymous
রুবিতে ভেরিয়েবল বাইন্ডিং এবং পিনিং
যেমন আমরা উপরের কিছু উদাহরণে দেখেছি, প্যাটার্ন ম্যাচিং প্রকৃতপক্ষে প্যাটার্নের কিছু অংশ নির্বিচারে ভেরিয়েবলে বরাদ্দ করার ক্ষেত্রে কার্যকর। একে ভেরিয়েবল বাইন্ডিং বলা হয়, এবং বিভিন্ন উপায়ে আমরা একটি ভেরিয়েবলকে আবদ্ধ করতে পারি:
- একটি শক্তিশালী টাইপের মিলের সাথে, যেমন
in [Integer => a]
অথবাin {a: Integer => a}
এ - টাইপ স্পেসিফিকেশন ছাড়া, যেমন
in [a, 1, 2]
অথবাin {a: a}
-এ . - ভেরিয়েবলের নাম ব্যতীত, যা ডিফল্ট কী নাম ব্যবহার করে, যেমন
in {a:}
-এa
নামের একটি ভেরিয়েবল সংজ্ঞায়িত করবে কীa
এ মান সহ . - বিশ্রাম বাঁধুন, যেমন
in [Integer, *rest]
-এ অথবাin {a: Integer, **rest}
এ .
কিভাবে, তাহলে, আমরা যখন একটি উপ-প্যাটার্ন হিসাবে একটি বিদ্যমান ভেরিয়েবল ব্যবহার করতে চাই তখন আমরা মেলাতে পারি? এটি যখন আমরা পরিবর্তনশীল পিনিং ব্যবহার করতে পারি ^
দিয়ে (পিন) অপারেটর:
a = 1
case {a: 1, b: 2}
in {a: ^a}
"matches"
end
এমনকি আপনি এটি ব্যবহার করতে পারেন যখন একটি ভেরিয়েবলকে একটি প্যাটার্নে সংজ্ঞায়িত করা হয়, যা আপনাকে এই মত শক্তিশালী প্যাটার্ন লিখতে দেয়:
case order
in {billing_address: {city:}, shipping_address: {city: ^city}}
puts "both billing and shipping are to the same city"
else
raise "both billing and shipping must be to the same city"
end
ভেরিয়েবল বাইন্ডিং এর সাথে উল্লেখ করার জন্য একটি গুরুত্বপূর্ণ ব্যঙ্গ হল যে প্যাটার্নটি পুরোপুরি মেলে না, তবুও ভেরিয়েবলটি আবদ্ধ থাকবে৷ এটি কখনও কখনও দরকারী হতে পারে৷
কিন্তু, বেশিরভাগ ক্ষেত্রে, এটি সূক্ষ্ম বাগগুলির কারণও হতে পারে — তাই নিশ্চিত করুন যে আপনি ছায়াযুক্ত পরিবর্তনশীল মানগুলির উপর নির্ভর করবেন না যা একটি ম্যাচের ভিতরে ব্যবহার করা হয়েছে৷ উদাহরণস্বরূপ, নিম্নলিখিতগুলিতে, আপনি শহরটি আশা করবেন "আমস্টারডাম" হবে, কিন্তু এর পরিবর্তে হবে "বার্লিন":
city = "Amsterdam"
order = {billing_address: {city: "Berlin"}, shipping_address: {city: "Zurich"}}
case order
in {billing_address: {city:}, shipping_address: {city: ^city}}
puts "both billing and shipping are to the same city"
else
puts "both billing and shipping must be to the same city"
end
puts city # Berlin instead of Amsterdam
রুবির কাস্টম ক্লাসের সাথে মিলে যাওয়া
আপনি রুবিতে কাস্টম ক্লাস প্যাটার্ন ম্যাচিং সচেতন করতে কিছু বিশেষ পদ্ধতি প্রয়োগ করতে পারেন।
উদাহরণস্বরূপ, একজন ব্যবহারকারীকে তার first_name
এর সাথে প্যাটার্ন মেলাতে এবং last_name
, আমরা deconstruct_keys
সংজ্ঞায়িত করতে পারি ক্লাসে:
class User
def deconstruct_keys(keys)
{first_name: first_name, last_name: last_name}
end
end
case user
in {first_name: "John"}
puts "Hey, John"
end
keys
deconstruct_keys
-এর যুক্তি প্যাটার্নে অনুরোধ করা কীগুলি রয়েছে৷ এটি সমস্তগুলি গণনা করা ব্যয়বহুল হলে রিসিভারের জন্য শুধুমাত্র প্রয়োজনীয় কীগুলি সরবরাহ করার একটি উপায়৷
deconstruct_keys
এর মতোই , আমরা deconstruct
এর বাস্তবায়ন প্রদান করতে পারি অবজেক্টগুলিকে একটি অ্যারে হিসাবে প্যাটার্নের সাথে মিলে যাওয়ার অনুমতি দেওয়ার জন্য৷ উদাহরণস্বরূপ, ধরা যাক আমাদের একটি Location
আছে যে শ্রেণীতে অক্ষাংশ এবং দ্রাঘিমাংশ আছে। deconstruct_keys
ব্যবহার করার পাশাপাশি অক্ষাংশ এবং দ্রাঘিমাংশ কী প্রদান করতে, আমরা [latitude, longitude]
আকারে একটি অ্যারে প্রকাশ করতে পারি পাশাপাশি:
class Location
def deconstruct
[latitude, longitude]
end
end
case location
in [Float => latitude, Float => longitude]
puts "#{latitude}, #{longitude}"
end
জটিল প্যাটার্নের জন্য গার্ড ব্যবহার করা
যদি আমাদের জটিল প্যাটার্ন থাকে যা নিয়মিত প্যাটার্ন ম্যাচ অপারেটরদের সাথে উপস্থাপন করা যায় না, আমরা একটি if
ব্যবহার করতে পারি (বা unless
) ম্যাচের জন্য গার্ড প্রদানের বিবৃতি:
case [1, 2]
in [a, b] if b == a * 2
"matches"
else
"no match"
end
=>
এর সাথে প্যাটার্ন ম্যাচিং /in
case
ছাড়া
আপনি যদি রুবি 3+ এ থাকেন, তাহলে আপনার কাছে আরও বেশি প্যাটার্ন ম্যাচিং ম্যাজিকের অ্যাক্সেস আছে। রুবি 3 থেকে শুরু করে, কেস স্টেটমেন্ট ছাড়াই একটি লাইনে প্যাটার্ন ম্যাচিং করা যেতে পারে:
[1, 2, "Three"] => [Integer => one, two, String => three]
puts one # 1
puts two # 2
puts three # Three
# Same as above
[1, 2, "Three"] in [Integer => one, two, String => three]
প্রদত্ত যে উপরের সিনট্যাক্সে একটি else
নেই ধারা, যখন ডেটা স্ট্রাকচার আগে থেকে জানা থাকে তখন এটি সবচেয়ে কার্যকর।
একটি উদাহরণ হিসাবে, এই প্যাটার্নটি একটি বেস কন্ট্রোলারের মধ্যে ভালভাবে ফিট হতে পারে যা শুধুমাত্র অ্যাডমিন ব্যবহারকারীদের অনুমতি দেয়:
class AdminController < AuthenticatedController
before_action :verify_admin
private
def verify_admin
Current.user => {role: :admin}
rescue NoMatchingPatternError
raise NotAllowedError
end
end
রুবিতে প্যাটার্ন ম্যাচিং:এই স্পেসটি দেখুন
প্রথমে, প্যাটার্ন ম্যাচিং বুঝতে কিছুটা অদ্ভুত বোধ করতে পারে৷ কারো কারো কাছে, এটি মহিমান্বিত বস্তু/অ্যারে ডিকনস্ট্রাকশনের মতো মনে হতে পারে৷
কিন্তু যদি এলিক্সিরের জনপ্রিয়তা কোনো ইঙ্গিত হয়, তাহলে প্যাটার্ন ম্যাচিং আপনার অস্ত্রাগারে থাকার জন্য একটি দুর্দান্ত হাতিয়ার। এলিক্সিরে এটি ব্যবহার করার অভিজ্ঞতা থাকলে, আমি নিশ্চিত করতে পারি যে একবার আপনি এটিতে অভ্যস্ত হয়ে গেলে এটি ছাড়া বেঁচে থাকা কঠিন।
আপনি রুবি 2.7 এ থাকলে, প্যাটার্ন ম্যাচিং (case
সহ /in
) এখনও পরীক্ষামূলক। রুবি 3 এর সাথে, case
/in
নতুন প্রবর্তিত একক-লাইন প্যাটার্ন ম্যাচিং এক্সপ্রেশনগুলি পরীক্ষামূলক হওয়ার সময় স্থিতিশীল অবস্থায় চলে গেছে৷ সতর্কতাগুলি Warning[:experimental] = false
দিয়ে বন্ধ করা যেতে পারে কোডে বা -W:no-experimental
কমান্ড-লাইন কী।
যদিও রুবিতে প্যাটার্ন ম্যাচিং এখনও তার প্রাথমিক পর্যায়ে রয়েছে, আমি আশা করি আপনি এই ভূমিকাটি দরকারী বলে মনে করেছেন এবং ভবিষ্যতের উন্নয়ন নিয়ে আপনিও ততটাই উত্তেজিত!
পি.এস. আপনি যদি রুবি ম্যাজিক পোস্টগুলি প্রেস থেকে বের হওয়ার সাথে সাথে পড়তে চান তবে আমাদের রুবি ম্যাজিক নিউজলেটারে সাবস্ক্রাইব করুন এবং একটি পোস্টও মিস করবেন না!