एक ब्लॉक कोड का एक टुकड़ा होता है जिसे किसी मेथड को पास किया जा सकता है।
दूसरी प्रोग्रामिंग भाषाओं में इसे अनाम फंक्शन या लैम्ब्डा कहा जाता है।
कोई मेथड किसी ब्लॉक को Proc में बदल सकता है, और 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 को कॉल नहीं किया जाए, तो वह ब्लॉक कभी नहीं चलेगा।
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 का इनिशियलाइज़र बनाइए। यह आर्गुमेंट के रूप में टास्क नामों के Strings का एक 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 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।