آشنایی با بلوک Ruby — به زبان ساده
یکی از قابلیتهای منحصر به فرد Ruby که غالباً به درستی درک نشده است، قابلیت بلوکهای Ruby است. در واقع بلوکها همان نسخه کلوژر در Ruby هستند و میتوان از آنها برای افزایش قابلیت استفاده مجدد از کد و کاهش حجم نوشتاری کد استفاده کرد. اما کلیدواژههایی مانند yield در نگاه نخست ممکن است غریب به نظر بیایند و موجب شوند که این کارکرد برای ما نامأنوس باشد. در این مقاله به توضیح مفاهیم مقدماتی بلوک Ruby میپردازیم تا دانشمان در این مورد افزایش یابد.
بلوک چیست؟
بلوکها تابعهای بینامی هستند که مقدار بازگشتی آنها روی متدی که آنها را فراخوانی کرده اعمال میشود. این تعریف شاید چندان روشن نباشد، بنابراین در ادامه آن را بیشتر توضیح میدهیم.
بلوکها شامل کدی بین مجموعهای از آکولادها یا یک جفت do/end هستند. آکولادها یک تعریف تکخطی ارائه میکنند و جفت do/end یک تعریف چندخطی در اختیار ما قرار میدهد:
1method { |i| ... }
2method do |i|
3 ...
4end
تعریف تکخطی به طور عمده برای کدهای تکخطی استفاده میشود. به منظور انسجام کد، در همه بخشهای این راهنما از ساختار do/end استفاده کردهایم. اگر قبلاً تجربه کار با روبی داشته باشید، حتماً با بلوکها مواجه شدهاید. متدهای each و map دو مورد از رایجترین تکرارکنندهها هستند که کاربرد بلوک را اجرا میکنند:
1"⭐️", "?"].each do |star|
2 puts star
3end
4# Output
5⭐️
6?
چگونه بلوک بسازیم؟
اگر بخواهیم یک تابع ساده بسازیم که یک ورودی که داخل “⭐️” قرار گرفته است را پرینت کند، باید چیزی مانند زیر بنویسیم:
1def star_wrap(el)
2 puts "⭐️" + el + "⭐️"
3end
4star_wrap("?")
5# Output
6⭐️?⭐
اگر بخواهیم این تابع را با استفاده از نمادگذاری بلوک بازنویسی کنیم، به صورت زیر عمل میکنیم:
1def star_wrap
2 puts "⭐️" + yield + "⭐️"
3end
4star_wrap do
5 "?"
6end
7# Output
8⭐️?⭐
چنان که میبینید، مقدار بازگشتی محتوای بلوک، آن چیزی است که به کلیدواژه yield تابع ارسال شده است. همچنین میتوانیم بلوک را با ارسال پارامترهایی به تابعی که ایجاد کردهایم، سفارشیسازی کنیم:
1def wrap_with(el)
2 puts el + yield + el
3end
4wrap_with("⭐️") do
5 "?"
6end
7# Output
8⭐️?⭐️
اگر بخواهیم به مقدار خروجی تابع خود در بلوک الصاقی ارجاع بدهیم، میتوانیم آرگومانهایی به yield ارسال کرده و در پارامترهای بلوک به آنها ارجاع بدهیم:
1def wrap_with(el)
2 puts el * 5
3 puts yield(el * 2)
4 puts el * 5
5end
6wrap_with("⭐️") do |els|
7 els + "?" + els
8end
9# Output
10⭐️⭐️⭐️⭐️⭐️
11⭐️⭐️?⭐️⭐️
12⭐️⭐️⭐️⭐️⭐️
مزیت بلوک نسبت به تابع معمولی چیست؟
تا به اینجا کار خاصی با بلوک انجام ندادهایم که یک متد معمولی نتواند انجام ندهد. بلوکها در مواردی که بخواهیم منطق اشتراکی را روی چارچوبهای مختلف به روشی آسان اعمال کنیم، مفید واقع میشوند.
برای نمونه فرض کنید، میدانیم که همواره باید خروجی یک سری از دستورها را بین یک مجموعه “⭐⭐⭐” پرینت کنیم. در این حالت میتوانیم از بلوکها استفاده کرده و منطق مورد نظر را روی چارچوبهای مختلف بدون نیاز به تابعهای کمکی اعمال کنیم:
1def star_wrap
2 puts "⭐⭐⭐"
3 puts yield
4 puts "⭐⭐⭐"
5end
6star_wrap do
7 server = ServerInstance.new
8 data = server.get("orange/heart/endpoint")
9 data.to_s
10end
11star_wrap do
12 fetcher = DatabaseFetcher.new
13 data = fetcher.load("purple_heart_data")
14 data.exists? data : "no heart data"
15end
16# Output (hypothetical)
17⭐⭐⭐
18?
19⭐⭐⭐
20
21⭐⭐⭐
22?
23⭐⭐⭐
چنان که در کد فوق میبینید ستارهها همواره پیش و پس از کد اجرا شده در بلوک پرینت میشوند. حتی با این که قلب قرمز به روشی بسیار متفاوت از قلب بنفش واکشی میشود، اما متد star_wrap به ما امکان میدهد که منطق پوششی ستاره را روی هر دو چارچوب به روشی یکسان اعمال کنیم.
مدیریت خطای بلوک
هر متدی میتواند یک بلوک بپذیرد، هر چند در تابع ارجاع نیافته باشد. در این حالت محتوای بلوک کاری انجام نمیدهد.
1def stars
2 puts "⭐⭐⭐"
3end
4stars do
5 puts "?"
6end
7# Output
8⭐⭐⭐
ما میتوانیم همه بلوکها را در مثالهای فوق فراخوانی کنیم، زیرا از کلیدواژه yield استفاده کردهایم. بنابراین شاید بپرسید اگر از کلیدواژه yield استفاده کنیم و بلوکی ارائه نکنیم، چه اتفاقی میافتد؟ در این حالت یک خطا ایجاد میشود.
1def star_wrap
2 puts "⭐️" + yield + "⭐️"
3end
4star_wrap
5# Output
6LocalJumpError: no block given (yield)
با استفاده از عبارت ?block_given برای بررسی کاربرد بلوک، میتوانیم از بروز این خطا جلوگیری کنیم:
1def star_wrap
2 if block_given?
3 puts "⭐️" + yield + "⭐️"
4 else
5 puts "⭐️⭐️⭐️"
6 end
7end
8star_wrap
9# Output
10⭐️⭐️⭐️
ارسال بلوک به عنوان یک پارامتر
اگر بخواهیم در مورد فراخوانی یک بلوک با صراحت عمل و یک ارجاع به آن بدهیم، باید آن را به صورت یک پارامتر به متدی ارسال کنیم:
1def star_wrap(&block)
2 puts "⭐️" + block.call + "⭐️"
3end
4star_wrap do
5 puts "?"
6end
7# Output
8⭐?⭐
در این نمونه، بلوک به شیء Proc تبدیل میشود که میتوان با استفاده از.call آن را فراخوانی کرد. استفاده از بلوکها به این ترتیب در مواردی مفید است که بخواهیم بلوکها را بین تابعها ارسال کنیم. بدین ترتیب پارامتر بلوک را به عنوان آخرین آرگومان و الصاق یک & ارسال میکنیم.
در ادامه متدهای star_wrap_a و star_wrap_b دقیقاً همین کار را انجام میدهند:
1def star_wrap_a(&block)
2 puts "⭐" + block.call("✨") + "⭐"
3end
4def star_wrap_b
5 puts "⭐" + yield("✨") + "⭐"
6end
7star_wrap_a do |el|
8 el + "?" + el
9end
10star_wrap_b do |el|
11 el + "?" + el
12end
13# Output
14⭐✨?✨⭐
15⭐✨?✨⭐
کاربرد عملی بلوکها
در یک اپلیکیشن پیشفرض روبی، نمای application.html.erb برای هر صفحهای که کنترلر آن از ApplicationController ارثبری میکند بارگذاری خواهد شد. اگر یک کنترلر فرزند ApplicationController اقدام به رندر یک نما بکند، محتوای آن به application.html.erb ارسال میشود. با توجه به این کارکرد، کد HTML آماده که باید روی همه صفحههای اپلیکیشن اعمال شود، به سهولت انجام مییابد.
1<!DOCTYPE html>
2<html>
3 <head>
4 <title>Block Investigation</title>
5 </head>
6<body>
7 <%= yield %>
8 </body>
9</html>
برای نمونه در وبسایت زیر، نوار فوقانی آبی رنگ در یک فایل نمای پایه قرار دارد که لیآوت متفاوتی برای هر مسیر ایجاد میکند.
سخن پایانی
لزومی نیست که بلوکها دارای منطق پیچیدهای باشد تا بتوانیم از آنها استفاده کنیم. بلوکها به طور معمول برای تجرید منطق مشترکی که باید روی چارچوبهای مختلف اعمال شود، مورد استفاده قرار میگیرند. اگر بلوکها را به طرز فکرشدهای بنویسیم، کد اصل DRY را بهتر رعایت میکند و خوانایی کد افزایش مییابد.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- راهنمای سریع متدهای آرایه در Ruby — از صفر تا صد
- مجموعه آموزشهای دروس علوم و مهندسی کامپیوتر
- ساخت اپلیکیشن اینستاگرام با Ruby on Rails (بخش اول) — از صفر تا صد
- ساختمان های داده در روبی (Ruby) — درخت های دودویی
==