ब्लॉक कोड का एक ऐसा हिस्सा है जिसे किसी मेथड को पास किया जा सकता है। दूसरी प्रोग्रामिंग भाषाओं में इसे अनाम फंक्शन या लैम्ब्डा कहा जाता है। कोई मेथड किसी ब्लॉक को प्रोक में बदल सकता है, जो कोड के एक ब्लॉक को दर्शाता है।
def my_method(&block)
1 + yield
end
my_method { 2 }
# => 3
क्रिस्टल में ब्लॉक और प्रोक बहुत आम हैं, जैसे इटरेशन और कॉलबैक के लिए। इनका इस्तेमाल DSL (डोमेन स्पेसिफिक भाषाएँ) बनाने में भी होता है। DSL के कुछ उदाहरण हैं वेब फ्रेमवर्क Kemal या टेस्टिंग फ्रेमवर्क Spec (यही आपके Crystal में लिखे हलों को टेस्ट करने के लिए इस्तेमाल होता है)।
ऐसा मेथड बनाने के लिए जो ब्लॉक ले सके, आपको आखिरी आर्गुमेंट से पहले एक एम्परसैंड (&) लगाना होता है। यह ब्लॉक को एक प्रोक में बदलकर उसे उस आर्गुमेंट में असाइन कर देता है। इसके बाद 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
बाकी स्पष्ट टाइप की तरह ही, कंपाइलर यह भी सुनिश्चित करता है कि टाइप वही हों जो अपेक्षित हैं। अगर ब्लॉक मेथड को दिए गए टाइप से मेल नहीं खाता, तो एक एरर मिलेगी।
ऐसे ब्लॉक के लिए एक शॉर्टहैंड होता है जो सिर्फ एक आर्गुमेंट लेता है और उस पर सिर्फ एक मेथड कॉल करता है। |x| इस्तेमाल करने के बजाय आप आर्गुमेंट के नाम से पहले & लगा सकते हैं।
def my_method(&block)
yield "a"
end
# This is:
my_method { |x| x.upcase }
# => "A"
# Is the same as:
my_method &.upcase
# => "A"
प्रोक का ब्लॉक से बहुत गहरा संबंध है। मुख्य अंतर यह है कि Proc एक टाइप है, जबकि ब्लॉक नहीं। Proc एक फंक्शन पॉइंटर को दर्शाता है और इसे किसी वेरिएबल की तरह इधर-उधर पास किया जा सकता है।
Proc बनाने के लिए आप प्रोक लिटरल ->(args) { ... } सिंटैक्स इस्तेमाल करते हैं। आर्गुमेंट कोमा (,) से अलग किए जाते हैं, और ब्लॉक { ... } से अलग किया जाता है। किसी प्रोक को चलाने के लिए आप call मेथड का उपयोग करते हैं और उसे आर्गुमेंट पास करते हैं। पैरामीटर के टाइप देना ज़रूरी है। रिटर्न टाइप प्रोक के बॉडी से अनुमानित किया जाता है, लेकिन इसे स्पष्ट रूप से भी दिया जा सकता है।
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
प्रोक उन मेथड को पास किए जा सकते हैं जो ब्लॉक की अपेक्षा करते हैं। जब किसी शर्त के आधार पर अलग-अलग ब्लॉक पास करने हों, तब यह काम आता है।
def my_method(&block)
yield 5
end
my_proc = -> (x) { x * 2 }
my_method(&my_proc)
# => 10
वेरिएबल घोषित करते समय आप वेरिएबल का टाइप बताने के लिए : Type सिंटैक्स इस्तेमाल कर सकते हैं। प्रोक के लिए आप Proc((ArgType, ...), ResultType) सिंटैक्स इस्तेमाल करते हैं। आखिरी टाइप हमेशा रिटर्न टाइप होता है और बाकी आर्गुमेंट के टाइप होते हैं। यानी अगर सिर्फ एक ही टाइप हो, तो वह रिटर्न टाइप होगा और प्रोक कोई आर्गुमेंट नहीं लेगा।
my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }
किसी मेथड से प्रोक घोषित करना भी संभव है।
def my_method(a, b)
a + b
end
my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)