Showing posts with label Ruby. Show all posts
Showing posts with label Ruby. Show all posts

Tuesday, January 23, 2007

Ruby 4

قسمت اول

قسمت دوم

قسمت سوم

Meta Programming

یکی از روش هایی که در Ruby زیاد استفاده می شود meta programming است. meta programming را بطور ساده می توان نوشتن کدی که کد دیگری را تولید می کند بیان کرد. meta programming در زبان های دیگر مانند جاوا و زبان های پلت فرم دات نت هم وجود دارد. در دات نت یک روش متداول برای این کار استفاده از Attribute ها است. یک مثال متداول استفاده از Attribute های WebMethod و WebService برای ساختن سرویس های وب هست. در جاوا تا قبل از JDK 1.5 روش متداول استفاده از XML برای این کار بود (برای مثال Axis برای تولید Stub های وب سرویس ها از XML استفاده می کرد) ولی بعد از معرفی Annotation ها(مکانیزمی شبیه Attribute ها در دات نت) در JDK 1.5 روش جایگزینی برای استفاده از XML مطرح شد. در هر حال ایده کلی برای meta programming در زبان های کامپایلری این است برنامه نویس اطلاعات اضافی را وارد کند(با استفاده از XML، Annotation، Attributes یا هر روش دیگری). سپس یک ابزار(مانند APT در جاوا) با بررسی این اطلاعات اضافی کدی را تولید کند که بتوان آنرا با استفاده از کامپایلر کامپایل کرد. روش های دیگری که برای meta programming در جاوا استفاده می شود تغیر در بایت کد کلاس ها هنگام load کردن کلاس ها، استفاده از dynamic proxy ها و تولید کلاس ها در زمان اجرا است که عمدتا روش های پیچیده ای هستند.

meta programming در Ruby بسیار ساده تر و متداول تر است. برای مثال مسئله دسترسی به فیلدهای یک شیئ را در نظر بگیرید. در جاوا راه حل متداول نوشتن متد های getter و setter و در C# استفاده از Property ها است. بطور معمول این مکانیزم ها یک کار را تکرار می کنند(getter ها مقدار فیلد را بر می گردانند و setter ها مقدار فیلد را تغییر می دهند). Ruby برای این کار راه حل جالبی دارد، در Ruby با استفاده از متدهای attr_reader و attr_writer می توان getter و setter و با استفاده از attr_accessor می توان هر دوی آنها را درست کرد. جالب این که این متد ها، متد های خاصی نیستند و تنها کاری که می کنند این است که رشته ای را درست کرده و برای اجرا به مفسر Ruby می دهند.

class FieldTest

attr_reader :r

attr_writer :w

attr_accessor :rw

def to_s

"@r, @w, @rw"

end

end

ft = FieldTest.new

ft.w = 111

ft.rw = "hello"

puts ft.r

یک راه حل ساده برای meta programming در Ruby استفاده از توابع eval است. این توابع معمولا یک رشته را به عنوان ورودی می گیرند و آن را اجرا می کند. فرض کنید می خواهیم کلاسی داشته باشیم که متد هایی با نام اعداد 1 تا 5 دارند و مقدار این اعداد را بر می گردانند، یک راه حل این است که 5 متد مختلف بنویسیم که مقادیر مورد نظر را بر می گردانند ولی یک راه حل بهتر در ادامه آورده شده:

class Numbers

nums = {'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5}

nums.each do |k, v|

module_eval "def #{k} \n #{v} \nend"

end

end

# Test class Numbers

n = Numbers.new

puts n.one # prints 1

puts n.two # prints 2

puts n.three # prints 3

puts n.four # prints 4

puts n.five # prints 5

متد module_eval رشته داده شده را به مفسر Ruby می فرستد تا آنرا اجرا کند. یک ویژگی بسیار مطلوب meta programming این است که حجم کد نوشته شده را بسیار کاهش می دهد(تصور کنید کلاس Numbers که در اینجا تنها در 4 خط نوشته شده در حالت عادی چه حجم زیادی می داشت) ولی ویژگی های مهم تری هم دارد که در ادامه به آن پرداخته ام.

Aspect Oriented Programming:

AOP یکی از روش های برنامه نویسی است که این روز ها خیلی در مورد آن صحبت می شود. AOP قابلیت های بسیاری به زبان های شیئ گرا اضافه می کند. بسیاری AOP را بعد از روش های ساخت یافته و شیئ گرا، بزرگترین تحول در دنیای برنامه نویسی دانسته اند. نکته جالب توجه این است که وقتی با Ruby کار می کنید، نیاز چندانی به AOP پیدا نمی کنید. عمده کارهایی که در AOP با آن مواجه هستید، اضافه کردن یک متد به یک کلاس، جایگزین کردن یک متد با یک متد جدید و اجرای یک کد قبل یا بعد از یک متد است. در Ruby به راحتی می توانید تمام این کارها را انجام دهید. مثلا فرض کنید بخواهید قبل از اینکه متد sort از کلاس Array فراخوانی شود، رشته ای را چاپ کنیم تا از فرا خوانی این متد باخبر شویم. کافی است کلاس Array را باز کنیم، متد sort را به old_sort تغییر نام دهیم و متد sort جدیدی را تعریف کنیم، در متد جدید کافی است رشته مورد نظر را چاپ کنیم و بعد متد قدیمی را فراخوانی کنیم:

class Array

alias_method :old_sort, :sort

def sort

puts 'Array.sort called !'

old_sort

end

end

#Test new sort

a = [3, 2, 4, 1, 4]

puts a.sort

# Output:

# Array.sort called !

# 1 2 3 4 4

البته یکی از ویژگی های زبانهایی که از AOP پشتیبانی می کنند(مانند AspectJ) پشتیبانی از wildcard ها است که چنین چیزی بطور استاندارد در Ruby پشتیبانی نمی شود.

Database:

یکی از مسائل مهمی که هر زبان برنامه نویسی باید برای حل آن فکری بکند کار کردن با پایگاه داده در آن زبان است. زبان های مختلف راهکار های متعددی برای این مسئله ارائه داده اند، در جاوا استاندارد ساده JDBC شاید اولین تلاش در این زمینه بود. بعد از آن ارائه EJB ها (با همه سر و صدایش) و سپس راه حل های مختلف بر پایه Object Relational Mapping مثل Hibernate، iBatis و Apache OJB نشان از تلاش های گسترده در این زمینه دارد. در دنیای دات نت هم کپی های این ORM ها مثل NHibernate و iBatis.NET، تکنولوژی هایی مثل ADO.NET و در نهایت ابتکار جدید ماکروسافت به نام LINQ وجود دارد. نکته مشترکی که در تمام این راه حل ها وجود دارد static بودن زبانی است که راه کار به وسیله آن پیاده سازی شده.

اما در پایگاه داده هم Ruby راه حل ساده خود را دارد. راه حل Ruby برای این کار ActiveRecord نام دارد. فرض کنید که در پایگاه داده خود جدولی به نام Students دارید که دو فیلد name و id دارد. قطعه کد زیر یک رکورد به جدول Students اضافه می کند:

ActiveRecord::Base.establish_connection(:adapter => "mysql",

:host => "127.0.0.1",

:username => "root",

:password => "",

:database => "Uni")

class Student <

ActiveRecord::Base

# Fields are fetched from database automatically

end

stu = Student.new

stu.name = 'mohsen'

stu.id = 12345

stu.save

هنگامی که کلاسی از ActiveRecord::Base ارث می برد، این کلاس بطور اتوماتیک فیلدهای پایگاه داده را به کلاس مورد نظر اضافه می کند، نکته جالب این است که ActiveRecord برای پیدا کردن فیلدها به دنبال جدولی هم نام با کلاس(ولی بصورت جمع) می گردد. این راه حل در Ruby زیاد استفاده می شود و باعث ساده شدن کار می شود. البته اگر جداول ما نام دیگری هم داشته باشند می توان ActiveRecord را برای استفاده از آن بکار برد ولی در این حالت باید اسم جدول برای آن مشخص شود.

همچنین ActiveRecord قادر است روابط یک به چند و چند به چند بین جداول را به کلاس های برنامه map کند، ولی در اینجا قصد پرداختن به جزئیات این مسئله را ندارم.

نکته ای که باید به آن توجه کرد این است که داینامیک بودن Ruby و امکان اضافه کردن فیلد به کلاس در زمان اجرا باعث شده بتوان چنین قابلیتی را پیاده سازی کرد و در زبانهای کامپایلری امکان پیاده سازی چنین راهکاری وجود ندارد.

Web:

شاید آن چیزی که باعث متداول شدن Ruby شد پلت فرم وبی بود که بر روی این زبان ایجاد شده بود. این پلت فرم Ruby on Rails نام دارد و تغیرات زیادی را در طرز تفکر توسعه دهندگان در دنیای وب ایجاد کرده. پرداختن به Ruby on Rails خود بحث مفصلی است که در این کوچک نوشته نمی گنجد!

معایب Ruby:

در پست اول در رابطه با Ruby گفتم که قصد دارم بیشتر در رابطه با مزایای این زبان صحبت کنم تا معایبش، ولی قول داده بودم حتما در قسمتی در مورد معایب این زبان هم توضیح دهم. برای همین هم این قسمت را در اینجا آوردم ولی راستش را بخواهید فعلا چیز زیادی در این رابطه در خاطرم نیست، ولی در هر حال چند نکته را می آورم.

نکته اول اینکه Ruby یک زبان اسکریپتی است و مسلما به مراتب کند تر از زبان های کامپایلری است، ولی این نکته را هم بگویم که سرعت اجرا یکی از فاکتورهای یک زبان است و بستگی زیادی به پیاده سازی آن دارد. امروزه با توجه به کاهش قیمت سخت افزار و افزایش سرعت کامپیوترها از اهمیت سرعت اجرا به شدت کاسته شده و فاکتورهای دیگری در زبان های برنامه نویسی اهمیت پیدا کرده اند(البته زبان های اسکریپتی را دست کم نگیرید و فراموش نکنید که ویکیپدیا یکی از بزرگترین سایت های اینترنت بر روی PHP کار می کند)

نکته دوم اینکه استفاده از یک زبان اسکریپتی به معنی این است که سورس کد برنامه شما در دسترس همه قرار دارد که در بسیاری از موارد قابل قبول نیست(البته می توان از تکنیک هایی مثل Obfuscation هم استفاده کرد ولی در هر حال این کار هم مشکلات خاص خود را دارد)

نکته سوم اینکه هنگامی که با یک زبان کامپایلری کار می کنید بسیاری از مشکلات احتمالی در زمان کامپایل چک می شود و در زمان اجرا با مشکلات کمتری روبرو هستید. برای پی بردن به بسیاری از مشکلات در Ruby باید برنامه خود را اجرا کنید. از این رو برنامه های نوشته شده به زبان Ruby وابستگی زیادی به Unit Testing و Code Coverage دارند.

نکته چهارم اینکه Ruby هنوز به اندازه لازم به تکامل نرسیده. هنوز انجام یک پروژه بزرگ با جاوا ریسک کمتری را تحمیل می کند. هنوز ابزار های توسعه در Ruby در اوایل کار خود قرار دارند و راه درازی تا رسیدن به آنجایی که امروزه ابزارهای زبان هایی مثل جاوا و دات نت قرار دارند باقی مانده.

کلام آخر:

راستش را بخواهید خیلی مسائل در مورد Ruby ماند که من نتوانستم در مورد آن بنویسم. فعلا نه وقت کافی برای این کار دارم و نه حال و حوصله این کار را. برای همین هم سعی کردم هر طور که شده این پست، آخرین پست من در باره این زبان باشد. حتما این موضوع را از طرز نوشته ام احساس کرده اید. در هر حال توصیه می کنم اگر به این زبان علاقه مند شده اید دست به کار شوید و مفسر Ruby و یک IDE مناسب را download کنید و شروع به کد نویسی کنید. در ابتدای کار شاید برایتان عجیب باشد که چقدر راحت می توان به این زبان کد نوشت.

مراجع:

[1] Beyond Java, By Bruce A. Tate, O'Reilly, September 2005, ISBN: 0-596-10094-9

[2] Ruby Cookbook, By Lucas Carlson, Leonard Richardson, O'Reilly, July 2006, Print ISBN-13: 978-0-59-652369-5

[3] Programming Ruby, The Pragmatic Programmers’ Guide, Second Edition, Dave Thomas

Wednesday, December 27, 2006

Ruby (3)

کد بلاک ها:

کد بلاک ها یکی از ساختارهای مهم Ruby هستند. کد بلاک قطعه ای کد هست که می تواند به عنوان یک آرگومان به یک تابع فرستاده شود. بسیاری از زبان ها ساختارهایی مشابه کد بلاک دارند، در جاوا Anonymous Inner Class در سی شارپ Delegate در C و C++ اشاره گر به توابع به جای کد بلاک ها استفاده می شود (البته این ساختارها خیلی با هم فرق می کنند اما کم و بیش برای یک هدف به کار می روند) . ساده ترین کاربردی که برای استفاده از کد بلاک ها می توان تصور کرد، Action Listener ها در واسط گرافیکی کاربر است. معمولا وقتی کاربر بر روی یک دکمه کلیک می کند می خواهیم کد دلخواه ما اجرا شود.

برای درک کد بلاک ها یک مثال می تواند کمک زیادی بکند، قطعه کد زیر یک تابع به نام one_to_ten تعریف می کند که یک کد بلاک را به عنوان آرگومان می گیرد. برای ارسال یک کد بلاک به یک تابع لازم نیست که کد بلاک بصورت صریح به عنوان آرگومان تابع بیان شود(گرچه می توان بصورت صریح نیز این کار را انجام داد)، بلکه هنگامی که از کلمه کلیدی yield در تابعی استفاده کنیم، مفسر Ruby بصورت ضمنی کد بلاکی را به عنوان آخرین آرگومان تابع در نظر می گیرد. مقادیری که در جلوی yield قرار می گیرند به عنوان آرگومان به کد بلاک ارسال خواهند شد.

def one_to_ten

for i in 1..10

yield i

end

end

one_to_ten { |arg| puts arg }

# result : 1 2 3 4 5 6 7 8 9 10

فراخوانی تابع one_to_ten باید شامل کد بلاکی باشد که به آن ارسال می شود. در این مثال کد بلاک ارسال شده آرگومانی با نام arg دارد و تنها کاری که انجام می دهد چاپ مقدار آرگومان است.

کد بلاک ها در Ruby بطور گسترده ای مورد استفاده قرار می گیرند. تقریبا همه کلاس های Ruby به شکلی از کد بلاک ها استفاده می کنند. برای مثال کلاس Array (و بسیاری دیگر از کلاس ها) متدی به نام each دارند که معمولا به جای حلقه foreach در دیگر زبان ها مورد استفاده قرار می گیرد. برای مثال قطعه کد زیر مجموع اعداد موجود در آرایه a را محاسبه می کند:

a = [1, 4, 3, 2]

s = 0

a.each {|i| s = s + i }

puts s # 10

همان طور که مشاهده می کنید کد بلاک ها می توانند به متغیر های خارج از کد بلاک نیز دسترسی داشته باشند و آنها را تغییر دهند. آرایه ها در Ruby متد های جالب دیگری نیز دارند ولی در اینجا قصد ندارم آنها را توضیح دهم، فقط به عنوان یک مثال دیگر، قطعه کد زیر را در نظر بگیرید، خط اول آن حاصل ضرب عناصر آرایه a و خط دوم حاصل جمع آن ها را محاسبه می کند، خط سوم تمام عناصر آرایه را به توان 2 می رساند و در آرایه جدیدی بر می گرداند و بلاخره خط چهارم تمام عناصر آرایه را که مقدار کوچکتر یا مساوی 3 دارند حذف می کند و نتیجه را در آرایه جدیدی بر می گرداند.

puts a.inject(1) { |mult, i| i * mult } # 24

puts a.inject(0) { |sum, i| i + sum } # 10

puts a.collect { |x| x * x } # [1, 16, 9, 4]

puts a.delete_if { |i| i <= 3 } # [4]

کلاس ها و اشیاء:

همان طور که پیش از این گفتم، Ruby یک زبان کاملا شیء گرا است. قطعه کد زیر کلاسی با نام Student تعریف می کند و سپس نمونه ای از کلاس درست کرده و از آن استفاده می کند. کلاس ها در Ruby با کلمه کلیدی class و متد ها با کلمه کلیدی def تعریف می شوند. سازنده کلاس در این زبان همیشه نام initialize را دارد. در Ruby نیازی به تعریف فیلد ها درون کلاس نداریم و با قرار دادهایی محدوده(scope) متغیر ها تعیین می شود. متغیر های سراسری با $، متغیر های استاتیک با @@ و فیلدهای کلاس با @ شروع می شود. متغیرهای محلی پیشوندی ندارند و ثابت ها(constants) با حروف بزرگ شروع می شوند.

class Student

def initialize(name, age, id)

@name, @age, @uid = name, age, id

@lessons = []

end

def << (lsn)

@lessons << lsn

end

def get_name

@name

end

def to_s

"#{@name}(id:#{@uid}, age:#{@age}, lessons:[#{@lessons.join(', ')}])"

end

end

stu = Student.new('mohsen', 23, 1)

stu << 'Math'

stu << 'Data Structure'

stu << 'OS'

puts stu

در Ruby همیشه آخرین خط یک متد به عنوان مقدار بازگشتی در نظر گرفته می شود(گرچه کلمه کلیدی return هم برای بازگرداندن مقدار از متد وجود دارد). در Ruby با عملگرها دقیقا مثل متد ها رفتار می شود(برای مثال stu<<'math' دقیقا مثل فراخوانی stu.<<('math') در نظر گرفته می شود) به همین دلیل عملگرها نیز می توانند به راحتی بصورت یک متد از کلاس تعریف شوند.

کلاس های Ruby در مقابل تغییرات باز هستند به همین دلیل کاربر می تواند به کلاس هایی که قبلا نوشته و حتی کلاس های کتابخانه این زبان متد ها و فیلد هایی را اضافه کند یا آن ها را override کند(بدون اینکه نیازی به ارث بری داشته باشد). این قابلیت خیلی قدرتمندی است که در بسیاری دیگر از زبان ها وجود ندارد. برای مثال قطعه کد زیر متدی به نام to_my_string به کلاس Array که یک کلاس از کتابخانه استاندارد Ruby هست اضافه می کند.

class Array

def to_my_string

# negative indices count from the end of the string

# no return keyword needed, always last line is returned automatically

str = '{'

each{|a| str += "#{a},"}

str[-1] = '}'

str

end

end

a = ['X', '1', '2', '3']

puts a.to_my_string # {X,1,2,3}

در Ruby حتی می توان متدی به یک شیء اضافه کرد یا یکی از متد های یک شیء را override کرد! برای مثال قطعه کد زیر متدی با نام hello به متغیر a اضافه می کند.

a = 'Mohsen'

def a.hello

'hello ' + self

end

puts a.hello # hello Mohsen

این قابلیت Ruby شاید در ابتدا عجیب و یا بدون استفاده به نظر برسد ولی این ویژگی نیز قابلیت بسیار قدرتمندی محسوب می شود. برای پی بردن به این موضوع فرض کنید می خواهیم در برنامه یک connection به پایگاه داده بسازیم که قابلیت بستن نداشته باشد(یعنی فراخوانی متد close باعث بسته شدن آن نشود). این مسئله را قبلا به زبان جاوا مطرح کرده ام و پاسخ آن را هم آورده ام. در جاوا برای اینکه تنها رفتار متد close از یک شیء connection را تغییر دهیم لازم است کد بسیار زیادی نوشته شود(delegate کردن تقریبا 50 متد از کلاس Connection، گرچه این کد را eclipse به آسانی تولید می کند و زحمت زیادی برای ما ندارد، ولی در هر حال حجم زیادی از کد برای این کار لازم است). در Ruby راه حل این مسئله می تواند خیلی ساده تر باشد:

$singleConnection = create_a_connection()

def $ singleConnection.close

# do nothing !

end

$ singleConnection.close() # result : do nothing!

در مجموع ویژگی هایی که در این بخش به آن پرداخته شد(کد بلاک ها، قرار داد های نام گزاری متغیر ها، باز بودن کلاس ها و اشیاء در مقابل تغییر و ...) تا حد بسیار زیادی می تواند حجم کد نویسی را در Ruby نسبت به دیگر زبان ها کاهش دهد.