مقدمه
روبی در لغت به معنای یاقوت است. هم چنین روبی نام زبانی اسکریپتی نیز هست که زبانی داینامیک، دارای reflection، عام منظوره و دارای شیگرایی است که از تلفیق syntax زبانهای perl و smalltalk به دست آمدهاست. هم چنین این زبان از Eifell و Lisp نیز تاثیر پذیرفته است. این زبان از پارادایمهای مختلفی شامل تابعی، شیگرا، دستوری و رفلکتیو پشتیبانی میکند.
نسخهی ۱.۸.۷ مفسر این زبان با زبان C پیادهسازی شدهاست. این زبان دارای dynamic type و مدیریت حافظهی خودکار است. همچنین مفسر این زبان single pass است.
تاریخچه
روبی در سال ۲۴ فوریهی سال ۱۹۹۳ توسط یوکیهیرو ماتسوموتو طراحی شد. هدف وی از طراحی این زبان جدید، برقراری تعادل میان برنامهنویسی دستوری و امری بود. ماتسوموتو میگوید:«من زبانی اسکریپتی را میخواستم که از Perl قدرتمندتر باشد و از پایتون شیگراتر. این دلیل تصمیم من است مبنی بر طراحی زبان شخصیام».
اولین release عمومی روبی در ۲۱ دسامبر ۱۹۹۵ در گروههای خبری در ژاپن اعلام شد. این release همزمان شد با راهاندازی یک mailing list برای روبی به زبان ژاپنی. در این مرحله روبی از بسیاری از امکاناتی که بسیاری از زبانها از آن برخوردارند، برخوردار بود. تعدادی از این امکانات عبارتاند از: شیگرایی، کلاسهای با وراثت، exception handling و جمعآوری زباله و ... .
روبی ۱.۰ در سال ۱۹۹۶ منتشر شد و در سال ۱۹۹۹ برای اولین بار یک mailing list به زبان انگلیسی به راه افتاد. از طرف دیگری کتابی به نام «Programming Ruby» به صورت رایگان در اختیار برنامهنویسان قرار گرفت که باعث سرعت گرفتن محبوبیت این زبان در میان انگلیسی زبانان شد.
روبی در نسخههای ۱.۲، ۱.۴، ۱.۶ و ۱.۸ به ترتیب در سالهای ۱۹۹۸، ۱۹۹۹، ۲۰۰۰ و ۲۰۰۳ منتشر شدند.
در نزدیکی سال ۲۰۰۵، RubyOnRails، یک framework برای تولید محصولات تحت وب نوشتهشد که باعث محبوبیت بیشتر روبی گردید.
در دسامبر سال ۲۰۰۷، روبی نسخهی ۱.۹ توزیع شد که تفاوتهای عمدهای با نسخههای قبلی داشت. نمونههایی از این تغییرات عبارتاند از:
- معرفی متغیرهای محلی، یعنی متغیرهایی که در بلاک تعریفشان موجودند،
- تعریف syntax جدید برای lambda
- پشتیبانی character encodingها
- واسط جدید socket
- ...
اکنون روبی ۲.۰ در حال طراحی است که گفته میشود ۱۰۰٪ با روبی ۱.۹.۳ (آخرین نسخهی توزیع شده) سازگاری دارد. قرار است این نسخه در فوریهی سال ۲۰۱۳ توزیع شود.
آموزش زبان روبی
روبی به دو صورت قابل اجرا است. یکی به صورت تعاملی(کنشی، interactive) و دیگری به صورت دستهای (batch). در اینجا ما کار با حالت تعاملی را آموزش میدهیم. حالت دیگر هیچ تفاوتی ندارد به جز اینکه باید همهی این دستورات درون یک یا چند فایل جداگانه نوشته شوند و سپس به مفسر داده شوند.
برای قرار گرفتن در حالت تعاملی، کافی است بعد از نصب روبی، از دستور irb در سامانههای یونیکس-مبنا استفاده کنیم. در این صورت، باید در ترمینال چیزی شبیه به این دیده شود:
irb(main):001:0>
در صورت مشاهدهی این رشته در ترمینال میتوانید از صحت نصب روبی خود مطمئن شوید. حال اگر در اینجا بنویسید "Hello World" و سپس دکمهی Enter را فشار دهید، خواهید دید:
irb(main):001:0> "Hello World"
=> "Hello World"
دقت کنید که چیزی که در اینجا نوشتهشد، صرفا «خروجی» عبارت ورودیاش را چاپ میکند. برای مثال:
irb(main):001:0> 2+3
=> 5
اگر بخواهیم عبارتی را در خروجی چاپ کنیم میتوانیم از دستور puts استفاده کنیم:
irb(main):001:0> puts "Hello World"
Hello World
=> nil
دقت کنید که این دستور، رشتهی ورودیاش را چاپ میکند، اما به عنوان یک تابع خروجی ندارد یا به عبارت دیگر خروجی آن nil است.
برای تعریف یک تابع کافی است از def و end استفاده کنیم. به مثال زیر توجه کنید:
irb(main):001:0> def h
irb(main):002:1> puts "Hello World!"
irb(main):003:1> end
=> nil
شاید بهتر باشد به این مثال کمی با دقت توجه کنیم. تعریف این تابع با عبارت def h آغاز شده است که بداهتا def کلید واژهی معرف شروع تعریف تابع و h نام تابع است. در اینجا نه پرانتزی قرار گرفتهاست و نه ورودیای. پس این تابع ورودی ندارد. نکتهای که لازم به ذکر است این است که پرانتزگذاری در این زبان هم کاملا اختیاری است. تا انجایی که ابهامی رخ ندهد، میتوان از پرانتزگذاری اجتناب کرد. حال اگر نیاز به ورودی بود صرفا نام متغیرها را بعد از h قرار میدهیم.
در این مثال به نکتهی دیگری نیز توجه کنید: در promptی که روبی بعد از ورود هر دستور میدهید، دو عدد وجود دارد: مانند 002:1. در اینجا 002 شمارندهی تعداد دستورات وارد شدهاست و 1 نشاندهندهی عمق کد ماست و یا این که در چه سطحی از بلاکینگ در حال برنامهنویسی هستیم.
حال به مثال زیر توجه کنید:
irb(main):001:0> def h name="fahime" # "fahime" is name's default value
irb(main):002:1> puts "Hello #{name.capitalize}!"
irb(main):003:1> end
=> nil
irb(main):004:0> h "nima"
Hello Nima!
=> nil
دقت کنید که عبارت {name.capitalize}# در داخل علامت نقل قول قرار گرفته است. با این وجود تابع capitalize را روی name صدا میزند. این ویژگی دقیقا به همین صورت در Perl نیز وجود دارد.
کمی شیگرایی: تعریف کلاس در این زبان کاملا شبیه پایتون است. به مثال زیر توجه کنید:
irb(main):001:0> class Greeter
irb(main):002:1> def initialize(name = "World")
irb(main):003:2> @name = name
irb(main):004:2> end
irb(main):005:1> def say_hi
irb(main):006:2> puts "Hi #{@name}!"
irb(main):007:2> end
irb(main):008:1> def say_bye
irb(main):009:2> puts "Bye #{@name}, come back soon."
irb(main):010:2> end
irb(main):011:1> end
=> nil
irb(main):012:0> g = Greeter.new ("Nima")
=> #<Greeter:0x16cac @name="Nima">
irb(main):013:0>g.say_hi
Hi Nima!
=> nil
تا اینجا نکاتی که تقریبا از syntax روشن است را ذکر میکنیم. دقت کنید که در اینجا هیچ حرفی از تایپ متغیرهایمان نزدهایم. در حقیقت هیچگاه نیز این کار را انجام نخواهیم داد. این زبان نیز از type checking پویا تبعیت میکند. البته type checking آن strong است، زیرا هیچ تایپی را به طور ضمنی به تایپ دیگری تبدیل نمیکند. از طرف دیگر، تعریف صریح صفات کلاس در خود کلاس نیامده است. بلکه در سازنده (همان initialize) گفتهایم name@ را برابر name قرار بده. بنابراین اشیا در این زبان پویا (dynamic) هستند. این ویژگی، در پایتون نیز وجود داشت. نکتهی جالب دیگری که در این زبان وجود دارد این است که برای مشخص کردن صفات (attributeها) از یک نماد @ در ابتدای نام صفت استفاده میکنیم. این @ از حرف اول Attribute گرفته شده است. (در مورد میزان پویایی این زبان بیش از این سخن خواهیم گفت). با وجود انعطافپذیری و شاید تا حدی شلختگی این زبان، هم چنان امکان دسترسی مستقیم به صفات وجود ندارد:
irb(main):014:0>g.@name
SyntaxError: compile error
(irb):52: syntax error
from (irb):52
فعلا برای مدتی این موضوع را فراموش کنید.
حال اگر بخواهیم لیست توابع موجود در یک کلاس را ببینیم کافی است تابع instance_methods را روی نام کلاس صدا کنیم:
irb(main):015:0> Greeter.instance_methods
=> ["method", "send", "object_id", "singleton_methods",
"__send__", "equal?", "taint", "frozen?",
"instance_variable_get", "kind_of?", "to_a",
"instance_eval", "type", "protected_methods", "extend",
"eql?", "display", "instance_variable_set", "hash",
"is_a?", "to_s", "class", "tainted?", "private_methods",
"untaint", "say_hi", "id", "inspect", "==", "===",
"clone", "public_methods", "respond_to?", "freeze",
"say_bye", "__id__", "=~", "methods", "nil?", "dup",
"instance_variables", "instance_of?"]
اما خوب، این توابع را کی و کجا پیادهسازی کردهایم؟ در حقیقت این لیست شامل توابع به ارث رسیده نیز میشود، برای این که این توابع نمایش داده نشوند چنین عمل میکنیم:
irb(main):016:0> Greeter.instance_methods(false)
=> ["say_bye", "say_hi"]
برای اینکه ببینیم آیا میتوان یک تابع را روی یک مصداق (instance) خاص از یک کلاس صدا زد یا نه میتوان ایندر حقیقت در هر زمان میتوان چنین عمل کرد:
irb(main):017:0> g.respond_to?("name")
=> false
irb(main):018:0> g.respond_to?("say_hi")
=> true
irb(main):019:0> g.respond_to?("to_s")
=> true
حال فرض کنید ما در این لحظه نیاز داریم که این قابلیت را به این کلاس بیفزاییم که امکان برگرداندن و یا حتی تغییر name را فراهم کند. چه باید کرد؟ میتوان این کلاس را در همین جا مجددا «باز کرد» و تغییراتی را در آن اعمال کرد! پویایی از این بیشتر؟
irb(main):020:0> class Greeter
irb(main):021:1> attr_accessor :name
irb(main):022:1> end
=> nil
توجه کنید که این تغییر اعمال شده، نه تنها در اشیای جدید، بلکه در اشیایی که از پیش ساخته شدهاند نیز اعمال خواهد شد(تکبیر)! در اینجا attr_accessor :name دو متد را به این کلاس میافزاید: یکی nameبرای گرفتن مقدار name@ و دیگری =name برای تغییر آن.
حال نوبت آن است که این زبان را به صورت غیر تعاملی نیز تجربه کنیم. با نوشتن quit یا exit میتوان از این محیط خارج شد. حال، در فایلی مانند nima.rb کد زیر را قرار دهید:
#!/usr/bin/env ruby
class MegaGreeter
attr_accessor :names
# Create the object
def initialize(names = "World")
@names = names
end
# Say hi to everybody
def say_hi
if @names.nil?
puts "..."
elsif @names.respond_to?("each")
# @names is a list of some kind, iterate!
@names.each do |name|
puts "Hello #{name}!"
end
else
puts "Hello #{@names}!"
end
end
# Say bye to everybody
def say_bye
if @names.nil?
puts "..."
elsif @names.respond_to?("join")
# Join the list elements with commas
puts "Goodbye #{@names.join(", ")}. Come back soon!"
else
puts "Goodbye #{@names}. Come back soon!"
end
end
end
if __FILE__ == $0
mg = MegaGreeter.new
mg.say_hi
mg.say_bye
# Change name to be "Zeke"
mg.names = "Zeke"
mg.say_hi
mg.say_bye
# Change the name to an array of names
mg.names = ["Albert", "Brenda", "Charles",
"Dave", "Englebert"]
mg.say_hi
mg.say_bye
# Change to nil
mg.names = nil
mg.say_hi
mg.say_bye
end
حال کافی است اجازهی اجرا شدن را به این فایل بدهید و در shell دستور nima.rb/. را اجرا کنید. دقت کنید که خط اول به shell میفهماند که باید روبی را صدا کند و آدرس این فایل را به عنوان تنها ورودی به وی بدهد. سپس روبی این فایل را باز و سپس اجرا میکند. دقت کنید که در روبی # علامتی است که مخصوص comment است. پس مشکلی در اجرای کد ایجاد نمیکند.
نظر نویسنده: شاید دلیل این که در این زبانها از # برای مشخص کردن comment استفاده میشود همین موضوع باشد. پایان نظر نویسنده!
یک نکتهی کوچک: نحوهی ایجاد مصداق: g = Greeter.new
حال باید به یک نکته در این کد بپردازیم:
@names.each do |name|
puts "Hello #{name}!"
end
در این سه خط که در تابع say_hi قرار گرفته است، ابتدا از name@ پرسیدهایم که آیا به each پاسخ میدهد یا خیر. بعد با فرض مثبت بودن جواب، این سه خط اجرا میشود. در اینجا آن قسمتی که بین do و end قرار دارد یک تابع lambda است که ورودی آن، name، از تابع each گرفته میشود. این زبان از توابع lambda نیز پشتیبانی میکند.
در مجموع، هر چند که این امکانات در این زبان، باعث راحت شدن کار برنامهنویس میشود(که این تا حدی مطابق (Responsible Design Principle است)، اما از سادگی کاسته و برنامه نویس برای فهمیدن رفتار برنامه باید تلاش بسیار کند که این خلاف Simplicity Principle است. همچنین این موضوع خلاف Impossible Error Principle نیز هست. زیرا مفسر هیچگونه تلاش موثری در راستای تشخیص خطای احتمالی توسط برنامهنویس نمیکند. از طرف دیگر، برای این که بفهمیم در یک کلاس چه متدی وجود دارد و چه متدی وجود ندارد، باید به زمان اجرا نگاه کنیم که خلاف Structure Principle است.
در هر صورت، خروجی برنامه به این قرار است:
Hello World!
Goodbye World. Come back soon!
Hello Zeke!
Goodbye Zeke. Come back soon!
Hello Albert!
Hello Brenda!
Hello Charles!
Hello Dave!
Hello Englebert!
Goodbye Albert, Brenda, Charles, Dave, Englebert. Come
back soon!
...
...
نکتهی دیگری که در این کد وجود دارد عبارت if __FILE__= $0 است. توجه به این نکته لازم است که شاید کسی بخواهد بعدا از این کد به عنوان کتابخانه استفاده کند. طبیعتا نباید هیچ کدی در این حالت اجرا شود. این شرط صرفا این را چک میکند که آیا این فایل، فایلی بوده که درخواست اجرای آن آمده یا خیر.
وراثت
روبی از وراثت چندگانه پشتیبانی نمیکند، اما از وراثت یگانه پشتیبانی میکند:
class A < B
....
مکانیزم رفع استثنا(Exception Handling)
این زبان نیز مانند بسیاری از زبانهای دیگر امکان جدا کردن قسمت اصلی برنامه و جریانهای شقهای دیگر (alternative flows) را در اختیار برنامهنویس قرار میدهد. این موضوع به خوانا شدن کد کمک میکند:
f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
else
puts "Congratulations-- no errors!"
ensure
f.close unless f.nil?
end
هر چند این زبان امکانات بسیار دیگری نیز دارد که به آنها اشاره نشد، اما عمدهی نکات مهم این زبان با مثال مطرح گردید و مابقی چیزی بیش از syntax صرف نیست.
منابع