একটি ব্লক হলো কোডের এমন একটি অংশ, যা একটি মেথডে পাস করা যায়। অন্য প্রোগ্রামিং ভাষায় এটিকে অ্যানোনিমাস ফাংশন বা ল্যামডা বলা হয়। একটি মেথড একটি ব্লককে Proc-এ রূপান্তর করতে পারে, যা কোডের একটি ব্লককে প্রকাশ করে।
def my_method(&block)
1 + yield
end
my_method { 2 }
# => 3
Crystal-এ ব্লক ও Proc অনেক বেশি ব্যবহৃত হয়, যেমন ইটারেশন আর কলব্যাকের মতো কাজে। তবে DSL (ডোমেইন স্পেসিফিক ভাষা) তৈরিতেও। DSL-এর কিছু উদাহরণ হলো ওয়েব ফ্রেমওয়ার্ক Kemal বা টেস্টিং ফ্রেমওয়ার্ক Spec (যা দিয়ে আপনার Crystal সমাধানগুলো টেস্ট করা হয়)।
এমন একটি মেথড তৈরি করতে যা একটি ব্লক নিতে পারে, আপনাকে শেষ আর্গুমেন্টের আগে একটি অ্যাম্পারস্যান্ড(&) যোগ করতে হবে।
এটি ব্লকটিকে একটি Proc-এ রূপান্তর করবে এবং সেটি আর্গুমেন্টে অ্যাসাইন করবে।
এরপর yield ব্যবহার করলে মেথডে দেওয়া ব্লকটি রান হবে।
def my_method(&block)
1 + yield
end
একটি ব্লক পাস করার দুটি উপায় আছে, হয় do ... end কিওয়ার্ড ব্যবহার করে, নয়তো কার্লি ব্রেস({}) ব্যবহার করে।
দুটির মধ্যে মূল পার্থক্য হলো প্রিসিডেন্স; do ... end একাধিক লাইনের ব্লক সমর্থন করে, কিন্তু কার্লি ব্রেস({}) করে না।
my_method do
2
end
# => 3
my_method { 2 }
# => 3
yield একটি কিওয়ার্ড, যা মেথডে দেওয়া ব্লকটি রান করে।
এর মানে হলো, yield কল করা না হলে ব্লকটি কখনোই রান হবে না।
এটিকে আর্গুমেন্টও দেওয়া যায়, যা ব্লকে পাস করা হবে।
ব্লকে যত খুশি আর্গুমেন্ট পাস করা যায়, এর কোনো সীমা নেই।
ব্লকে আর্গুমেন্টগুলো ব্যবহার করতে আপনি |x, y, ...| সিনট্যাক্স ব্যবহার করেন।
এখানে x, y, ... হলো আর্গুমেন্টগুলোর নাম।
def my_method(&block)
1 + yield 2
end
# With curly braces:
my_method { |x| x * 3 }
# => 7
# With do ... end:
my_method do |x|
x * 3
end
# => 7
উপরের উদাহরণে ব্লকটিকে আর্গুমেন্ট 2 দেওয়া হয়েছে, ব্লকটি সেটিকে 3 দিয়ে গুণ করে 6 রিটার্ন করে।
এরপর মেথডটি ফলাফলের সাথে 1 যোগ করে 7 রিটার্ন করে।
yield-কে দেওয়া আর্গুমেন্টের সংখ্যা ব্লক যত আর্গুমেন্ট আশা করে তার চেয়ে বেশি হলে একটি এরর রেইজ হবে।
def my_method(&block)
1 + yield
end
my_method { |x| x * 3 }
# Error: too many block parameters (given 1, expected maximum 0)
ব্লক এক্সপ্লিসিট বা ইমপ্লিসিটভাবে লেখা যায়। ইমপ্লিসিট ব্লকই সবচেয়ে কমন, আর উপরের উদাহরণগুলোতে যেগুলো দেখানো হয়েছে সেগুলোই ইমপ্লিসিট। আপনি ব্লককে বিভিন্ন ধরনের আর্গুমেন্টও দিতে পারেন, তখন কম্পাইলার টাইপগুলো অনুমান করে একটি ইউনিয়ন টাইপ তৈরি করবে।
def my_method(&block)
yield "a"
yield 1
end
my_method do |x|
p typeof(x)
end
# output: (String | Int32)
এক্সপ্লিসিট ব্লক লেখার সময় আপনি আর্গুমেন্টগুলোর টাইপ এবং রিটার্ন টাইপ নির্দিষ্ট করতে পারেন।
এটি Arg1Type, Arg2Type, ... -> ResultType সিনট্যাক্স দিয়ে করা হয়।
এখানে আর্গুমেন্টগুলো কমা(,) দিয়ে আলাদা করা হয়, আর রেজাল্ট টাইপ আলাদা করা হয় -> দিয়ে।
def my_method(&block : Int32, Int32 -> Int32)
1 + yield(2, 3)
end
my_method { |x, y| x * y }
# => 7
অন্য এক্সপ্লিসিট টাইপের মতোই কম্পাইলার নিশ্চিত করবে যে টাইপগুলো প্রত্যাশিত। ব্লক যদি মেথডে দেওয়া টাইপের সাথে না মেলে, তাহলে একটি এরর রেইজ হবে।
Proc-এর সাথে ব্লকের খুব গভীর সম্পর্ক রয়েছে।
মূল পার্থক্য হলো, Proc একটি টাইপ, কিন্তু ব্লক নয়।
একটি Proc একটি ফাংশন পয়েন্টারকে প্রকাশ করে এবং একটি ভ্যারিয়েবল হিসেবে এদিক-ওদিক পাস করা যায়।
একটি Proc ডিফাইন করতে আপনি proc লিটারেল ->(args) { ... } সিনট্যাক্স ব্যবহার করেন।
আর্গুমেন্টগুলো কমা(,) দিয়ে আলাদা করা হয়, আর ব্লক আলাদা করা হয় { ... } দিয়ে।
একটি Proc ইনভোক করতে আপনি call মেথড ব্যবহার করেন এবং এতে আর্গুমেন্টগুলো পাস করেন।
প্যারামিটারগুলোর টাইপ বাধ্যতামূলক।
রিটার্ন টাইপ proc-এর বডি থেকে অনুমান করা হয়, তবে স্পষ্টভাবেও দেওয়া যায়।
my_proc = ->(x : Int32, y : Int32) : Int32 { x * y }
my_proc.call(2, 3)
# => 6
ব্লক ক্যাপচার করা হলো একটি ব্লক থেকে Proc তৈরি করার একটি উপায়।
এটি একটি মেথডের শেষ আর্গুমেন্টের আগে & অপারেটর ব্যবহার করে করা হয়।
এটি ব্লকটিকে একটি Proc-এ রূপান্তর করবে এবং সেটি আর্গুমেন্টে অ্যাসাইন করবে।
def my_method(&block)
block.call
end
my_method { 1 + 2 }
# => 3
যেসব মেথড একটি ব্লক আশা করে, সেগুলোতে Proc পাস করা যায়। কোনো শর্তের ভিত্তিতে ভিন্ন ভিন্ন ব্লক পাস করতে চাইলে এটি কাজে লাগতে পারে।
def my_method(&block)
yield 5
end
my_proc = -> (x) { x * 2 }
my_method(&my_proc)
# => 10
ভ্যারিয়েবল ডিক্লেয়ার করার সময় আপনি ভ্যারিয়েবলের টাইপ ডিক্লেয়ার করতে : Type সিনট্যাক্স ব্যবহার করতে পারেন।
Proc-এর ক্ষেত্রে আপনি Proc((ArgType, ...), ResultType) সিনট্যাক্স ব্যবহার করেন।
শেষ টাইপটি সবসময় রিটার্ন টাইপ হবে, আর বাকিগুলো হবে আর্গুমেন্ট টাইপ।
অর্থাৎ যদি কেবল একটি টাইপ থাকে, সেটিই রিটার্ন টাইপ হবে এবং proc কোনো আর্গুমেন্ট নেবে না।
my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }
আপনি একটি ওয়েবসাইটের জন্য একটি কনটেন্ট ম্যানেজমেন্ট সিস্টেম তৈরি করছেন, যেখানে আর্টিকেল, ব্লগ পোস্ট আর অন্যান্য লেখা কনটেন্ট থাকে। আপনাকে একটি টাস্ক হ্যান্ডলার তৈরি করার দায়িত্ব দেওয়া হয়েছে, যা একটি প্রায়োরিটি কিউ-তে থাকা টাস্কগুলো প্রসেস করবে। টাস্ক হ্যান্ডলার টাস্ক পাবে এবং সার্ভারের লোড ও টাস্কের প্রায়োরিটির ভিত্তিতে ঠিক করবে এরপর কোন টাস্কটি এক্সিকিউট করবে। সার্ভারে যদি প্রচুর লোড থাকে, তাহলে টাস্কটি এক্সিকিউট হওয়ার জন্য প্রায়োরিটি নম্বরটি আরও বেশি হতে হবে। সার্ভার লোডের ভিত্তিতে ডাইনামিকভাবে লোড বদলে দেবে।
TaskHandler ইনিশিয়ালাইজ করুনTaskHandler-কে একটি কিউ আর একটি ডিফল্ট load_level দিয়ে ইনিশিয়ালাইজ করতে হবে।
TaskHandler ক্লাসের ইনিশিয়ালাইজারটি ইমপ্লিমেন্ট করুন, যা আর্গুমেন্ট হিসেবে টাস্কের নামের Stringগুলো নিয়ে গঠিত একটি Array নেয় এবং সেটি @tasks নামের একটি ইনস্ট্যান্স ভ্যারিয়েবলে রাখে।
ইনিশিয়ালাইজারটি @task_condition_logic নামের ইনস্ট্যান্স ভ্যারিয়েবলও তৈরি করবে, যেটি একটি Proc রাখে। ওই Proc-টি একটি Int32 নেয় এবং আর্গুমেন্টটি 0-এর সমান বা তার চেয়ে বড় হলে ট্রু রিটার্ন করে, আর 0-এর চেয়ে ছোট হলে ফলস রিটার্ন করে।
task_handler = TaskHandler.new(["task1", "task2", "task3"])
# => #<TaskHandler:0x7fd8d4cfbf00 @tasks=["task1", "task2", "task3"], @task_condition_logic=#<Proc(Int32, Bool):0x562536a4a330>>
সার্ভারকে সার্ভারের লোডের ভিত্তিতে টাস্ক কন্ডিশন লজিক আপডেট করার সুযোগ থাকতে হবে।
TaskHandler#update_task_condition_logic মেথডটি ইমপ্লিমেন্ট করুন, যা আর্গুমেন্ট হিসেবে একটি ব্লক নেয় এবং ব্লকটি @task_condition_logic ইনস্ট্যান্স ভ্যারিয়েবলে রাখতে হবে।
task_handler = TaskHandler.new(["task1", "task2", "task3"])
task_handler.update_task_condition_logic { |load| load > 1 }
# => #<TaskHandler:0x7f068118cf00 @tasks=["task1", "task2", "task3"], @task_condition_logic=#<Proc(Int32, Bool):0x562df99d0370>>
সার্ভারকে কিউ-তে থাকা পরের টাস্কটি এক্সিকিউট করার সুযোগ থাকতে হবে।
@tasks অ্যারের প্রথম টাস্কটিই পরের এক্সিকিউট হওয়ার টাস্ক।
টাস্কটির প্রায়োরিটি লেভেল @task_condition_logic প্রসে পাঠালে যদি ট্রু রিটার্ন করে, তখনই টাস্কটি এক্সিকিউট করা উচিত।
টাস্কটি এক্সিকিউট হয়েছে কি না, তার ভিত্তিতে এটি মেসেজ রিটার্ন করবে।
টাস্কটি এক্সিকিউট হলে এটি রিটার্ন করবে: "Completed <task>"।
টাস্কটি এক্সিকিউট না হলে এটি রিটার্ন করবে: "Could not complete <task>"।
টাস্কটি এক্সিকিউট হলে সেটি @tasks অ্যারে থেকে মুছে ফেলা হবে।
TaskHandler#execute_next_task মেথডটি ইমপ্লিমেন্ট করুন, যা আর্গুমেন্ট হিসেবে পরের টাস্কের প্রায়োরিটি লেভেল Int32 হিসেবে নেয় এবং টাস্কটি এক্সিকিউট হয়েছে কি না তার ভিত্তিতে একটি String রিটার্ন করে।
task_handler = TaskHandler.new(["task1", "task2", "task3"])
task_handler.update_task_condition_logic { |load| load > 1 }
task_handler.execute_next_task(2)
# => "Completed task1"
task_handler.execute_next_task(1)
# => "Could not complete task2"
Exercism-এ সাইন আপ করুন, Crystal ট্র্যাকের 26টি কনসেপ্ট142টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।