Friday, August 12, 2005

DSL with Ruby

Martin Fowler เขียนบทความ Using the Rake Build Language
มีประเด็นที่น่าสนใจอยู่อันก็คือ Domain Specific Language

The basic idea of a domain specific language (DSL) is a computer language that's targeted to a particular kind of problem, rather than a general purpose language that's aimed at any kind of software problem.

ที่สนใจประเด็นนี้ เพราะว่า Ruby language มันมีความยืดหยุ่นมาก
ทำให้เราสามารถสร้าง Domain Language เพื่อให้เหมาะกับงานของเราได้
อย่างเช่น Rake ที่ออกแบบมาสำหรับ build process
หรือ ActiveRecord ที่ออกแบบมาสำหรับ ORM

ตัวอย่าง Rakefile
task :codeGen do
# do the code generation
end

task :compile => :codeGen do
#do the compilation
end

task :dataLoad => :codeGen do
# load the test data
end

task :test => [:compile, :dataLoad] do
# run the tests
end


ตัวอย่าง ActiveRecord
class Item < ActiveRecord::Base
belongs_to :category
validates_associated :categoory
validates_format_of :done_before_type_case
,:with=>/[01]/,
,:message=>"must be 0 or 1"
validates_presence_of :description
validates_length_of :description
,:maximum=>40

end


จริงๆแล้วพวก syntax ที่เราเห็นนั้น มันก็คือ method ธรรมดานี่แหล่ะ
อย่างเช่น
task :name => [:prereq1, :prereq2]

จริงๆแล้ว ถ้าเขียนให้ดูเป็นขั้นเป็นตอนหน่อย ก็เขียนได้ดังนี้
hash = Hash.new
hash[:name] = [:prereq1, :prereq2]
task(hash)

ถ้าเราไปเปิด source code ของ rake ดูจะเห็นว่า
เขา define method task ไว้ดังนี้
# Declare a basic task.
#
# Example:
# task :clobber => [:clean] do
# rm_rf "html"
# end
#
def task(args, &block)
Task.define_task(args, &block)
end

จะเห็นได้ว่า block do...end ถูก pass เป็น object ไปไว้ในตัวแปร block
เมื่อต้องการ execute ก็เพียงแต่สั่ง block.call

ใน Rake มี feature Multiple Definitions
ซึ่งเปิดโอกาสให้เราเขียน definition กระจายแบบนี้ได้
task :name
task :name => [:prereq1]
task :name => [:prereq2]
task :name do |t|
# actions
end


feature ที่ทำให้เขียนแบบนี้ได้ มันเป็นคุณสมบัติของ ruby language เลย เช่น
class Person 
def initialize(name)
@name = name
end
end

p1 = Person.new("polawat")

class Person
def sayhi
puts "Hello #{@name}."
end
end

p1.sayhi

เห็นได้ว่า เราสามารถ extend class Person
ด้วยการประกาศ definition class ใหม่อีกรอบ
(ไม่มีข้อจำกัดเรื่อง scope เราสามารถ extend class
ของ ruby เช่น String ก็ได้)


อ่านเพิ่มเติม

Related link from Roti

No comments: