把你的 programming Language 特化成專門對你要處理的 Domain 的語言( Domain Specifi Language),然後再用這種語言去處理你的問題。
著名的 Lisp 駭客 Paul Graham 在駭客與畫家這本書中說:
In Lisp, you don‘t just write your program down toward the language. you also build the language up toward your program.
Ruby 也同樣的有這樣的特性。
最簡單的 Metaprogramming in Ruby,就是 attr_reader, attr_writer, attr_accessor。在 Ruby 裡所有的 instance variable 都是必須用 getter 和 setter 來存取。attr_xxxx 就是用來生成這些 getter 和 setter 的。
例如:
class MyClassattr_accessor :niceenda = MyClass.newa.nice = 100puts a.nice
就等同於
class MyClassdef nice #getter for instance variable nice@niceenddef nice=(num) #setter for instance variable nice@nice = numendenda = MyClass.newa.nice = 100puts a.nice
這裡有幾個要說明的地方
我們也可以自己寫出類似這樣的 method。比如我們常常有些 instance variable 是一個 flag,在 Ruby 中會傳回真假值的 method 通常以 ? 做結尾?,F(xiàn)在希望 Ruby 幫我們自動生成這些以 ? 結尾的 method,就是要能辦到像下面這樣:
class Workattr_flag :done ,:startendw = Work.newputs "Yes, the work is started" if w.start?puts "No, the work is not done" if !w.done?
attr_flag 的寫法如下:
class Moduledef attr_flag(*args)args.each do |sym|class_eval %{def #{sym}?@#{sym}end}endendend
同樣的功能用 define_method 也可以做到
class Moduledef attr_flag(*args)args.each do |sym|define_method("#{sym}?") { instance_variable_get "@#{sym}"}endendend
用 define_method 好像比較簡潔,但是兩種方法都有人用。
ruby 本身並沒有提供 method overload 的功能。因為 Ruby 是一個 dynamic type 的語言,并具有duck typing 所以不需要像 method overload 這樣的功能。不過因為 Ruby 是非常動態(tài)的語言,幫他加上 overload 的功能其實很簡單。
先來看看最我們要達到什麼效果: class Test# 這是我們要寫的 OverLoad Moduleinclude OverLoad# 一般的 method 定義def t1puts ‘original t1‘end# 直接可以用 block 來 overload t1 這個函數(shù)# 第一個參數(shù)是要被 overload 的 method 名# 接下來是任意個數(shù)的參數(shù),用來指用 overload method 的參數(shù)型態(tài)(這裡是沒有指定)# 最後是一個 block,也就是 method 的內容overload :t1 do |a|puts aend# 當然可以 overload 很多次overload :t1 do |a,b|puts a+bend# 這裡就有指定參數(shù)的型態(tài)了 (String, String)overload :t1, String, String do |a,b|puts "the String is #{a} #"endend
下面就是 OverLoad Module:
值得注意的是,在程式中的 Overload_Dispetch_Db 不能直接使用,要用 const_get 和 const_set 來存取。這是因為變數(shù)的 scope 的關係。在 class.eval 的 block 中,直接使用會被以為是 module OverLoad 的 Constant 而出現(xiàn)問題。 module OverLoadprivate# 這是將 overload 所接到的「參數(shù)型態(tài)(spec)」轉成一個字串# 這個字串在等一下動態(tài)產(chǎn)生程式碼的時候會用到def self.spec_to_code(spec)if Integer === spec"args.length == #{spec}"elsif Array === speci = -1spec.map {|ts| "#{ts.inspect} === args[#{i+=1}]"}.join(" && ")endend# included 是一個 Hook Method,每當有 class include 這個 module 時# 這個 function 就會被呼叫# include 這個 module 的 class 會被當成參數(shù)傳進來,也就是這裡的 the_classdef self.included(the_class)#這裡幫 include 這個 module 的 class 加上一些新的 method#the_class.class_eval { ooxx} 可以想成把 ooxx 直接寫在 the_class 的內容裡the_class.class_eval do#新加上的函式都是 private 的比較安全private# 加上 Overload_Dispetch_Db 這個 Hash# 用來存 method_spec 和真的 method 的對映 (其實這裡存的不是 Method,而是 Proc)self.const_set("Overload_Dispetch_Db", Hash.new)# 加上 overload 這個 class method# 以傳入的 [method名, 參數(shù)名字] 做為 key, 將 blok 存入 Hash 之中def self.overload(method_id, *dispatch_spec, &block)dd = self.const_get("Overload_Dispetch_Db")if dispatch_spec ==[]dd[[method_id,block.arity]] = blockelsedd[[method_id,dispatch_spec]] = blockend# 把原來的 method 存起來# 執(zhí)行時若是在 Overload_Dispetch_Db 裡找不到要求的 method 的話# 就會呼叫這個原來的這個 methodif method_defined?(method_id) && !method_defined?("old_#{method_id}")alias_method "old_#{method_id}", method_idend# 動態(tài)的生成 methodbody = ""# 首先把 Hash 根據(jù)參數(shù)型態(tài)做排序# 然後一個一個的產(chǎn)生程式碼存到 body 之中# 這裡的排序是把有指定型態(tài)的排在前面dd.sort do |a,b|if Array === a[0][1]Array === b[0][1] ? (a[0][1].length <=> b[0][1].length) : -1elseArray === b[0][1] ? 1 : a[0][1] <=> b[0][1]endend.each_with_index do |((mid,spec), target), i|body<<<<-EOS#{i == 0 ? ‘if‘ : ‘elsif‘} #{OverLoad.spec_to_code(spec)}Overload_Dispetch_Db[[:#{mid},#{spec.inspect}]].call(*args)EOSendmethod = <<-EOSdef #{method_id.to_s}(*args)#{body}elseif respond_to?:old_#{method_id}old_#{method_id}(*args)elseraise "Could not dispatch"endendendEOS# 先把原來的 method undefine# 再把做出來的 code 加到目前的 class 中remove_method(method_id)class_eval methodendendendend
以之前舉的例子來看,最後 Test 裡的的 t1 會是這個樣子: def t1(*args)if String === args[0] && String === args[1]Overload_Dispetch_Db[[:t1,[String, String]]].call(*args)elsif args.length == 1Overload_Dispetch_Db[[:t1,1]].call(*args)elsif args.length == 2Overload_Dispetch_Db[[:t1,2]].call(*args)elseif respond_to?:old_t1old_t1(*args)elseraise "Could not dispatch"endendend