今天又學了一會RUBY的閉包,主要是看《RUBY元編程(metapromgramming ruby)》一書:
http://book.douban.com/subject/4086938/
第三章閉包結尾的守關BOSS是一道題:編寫你的第一種領域專屬語言。
event "the sky is falling" do @sky_height < 300endevent "It's getting closer" do @sky_height < @mountains_heightendsetup do puts "Setting up sky" @sky_height = 100endsetup do puts "Setting up mountains" @mountains_height = 200end
要求編寫一個程式:redflag.rb. 對上面這段測試檔案運行,得到如下的輸出
Setting up skySetting up mountainsAlert: the sky is fallingSetting up skySetting up mountainsAlert: It's getting closer
原書作者的給出的答案如下:
def event(name,&block) @events[name] = block enddef setup(&block) @setups.push(block)endDir.glob('*event.rb').each do |file| @events={} @setups=[] load file #puts file @events.each do |name,event| env = Object.new() @setups.each do |setup| env.instance_eval &setup end puts "Alert: #{name}" if env.instance_eval &event end #puts @events.to_s #puts @setups.to_send#我加的這兩行,用來測試@sky_height的範圍puts @sky_height puts @mountains_heigh
前面的都好理解,關鍵是後來做的這個Clean Room:
env = Object.new() @setups.each do |setupa| env.instance_eval &setup end puts "Alert: #{name}" if env.instance_eval &event
這一段,主要是為了讓 &setup 這個區塊與 &event 區塊在同一個對象env的空間內運行,達到來共用兩個變數的值:@sky_height , @mountains_height的目的。
我去掉了這個clean room後,改為proc.call的方式做了下面的這個測試:
def event(name,&block) @events[name] = block enddef setup(&block) @setups.push(block)endDir.glob('*event.rb').each do |file| @events={} @setups=[] load file #puts file @events.each do |name,event| env = Object.new() @setups.each do |setup| setup.call end puts "Alert: #{name}" if event.call end #puts @events.to_s #puts @setups.to_send
#我加的這兩行,用來測試@sky_height的範圍
puts @sky_height
puts @mountains_heigh
也能通過。不過這時候發現這兩個變數@sky_height @mountains_heigh已經變成一個全域變數--proc層級的變數。在程式的末尾打出了變數的值。
而用作者的潔淨室方法,這兩個變數只是在env的上下文環境中存在,是這個Object對象的執行個體變數。在程式的末尾這兩個變數是nil。
作者通過這個例子極好地展示了 潔淨室 和 扁平範圍 的功能。
這章的最後,作者給出了另外一個更完美的方法,連@events @setups 這兩個全域變數也去掉了:
#---# Excerpted from "Metaprogramming Ruby",# published by The Pragmatic Bookshelf.# Copyrights apply to this code. It may not be used to create training material, # courses, books, articles, and the like. Contact us if you are in doubt.# We make no guarantees that this code is fit for any purpose. # Visit http://www.pragmaticprogrammer.com/titles/ppmetr for more book information.#---lambda { setups = [] events = {} Kernel.send :define_method, :event do |name, &block| events[name] = block end Kernel.send :define_method, :setup do |&block| setups << block end Kernel.send :define_method, :each_event do |&block| events.each_pair do |name, event| block.call name, event end end Kernel.send :define_method, :each_setup do |&block| setups.each do |setup| block.call setup end end}.callDir.glob('*events.rb').each do |file| load file each_event do |name, event| env = Object.new each_setup do |setup| env.instance_eval &setup end puts "ALERT: #{name}" if env.instance_eval &event endend
附:關於instance_eval的解釋:
instance_eval可以在一個執行個體的上下文中eval一個字串或者一個block:
instance_eval()方法做下面3件事情:
a,改變self為instance_eval的接收器。
b,改變預設的definee給接收器的eigenclass,如果沒有,則建立它。
c, 執行block的內容。
參考:http://book.douban.com/subject/4086938/annotation?sort=rank&start=20 blackanger 的書評