RUBY元编程学习之”编写你的第一种领域专属语言“

今天又学了一会RUBY的闭包,主要是看《RUBY元编程(metapromgramming ruby)》一书:

http://book.douban.com/subject/4086938/

第三章闭包结尾的守关BOSS是一道题:编写你的第一种领域专属语言。

event "the sky is falling" do

    @sky_height < 300

end



event "It's getting closer" do

    @sky_height < @mountains_height

end



setup do

    puts "Setting up sky"

    @sky_height = 100

end



setup do

    puts "Setting up mountains"

    @mountains_height = 200

end

  要求编写一个程序:redflag.rb. 对上面这段测试文件运行,得到如下的输出

Setting up sky

Setting up mountains

Alert: the sky is falling

Setting up sky

Setting up mountains

Alert: It's getting closer

  

     原书作者的给出的答案如下:

def event(name,&block)

    @events[name] = block 

end



def setup(&block)

    @setups.push(block)

end



Dir.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_s

end



#我加的这两行,用来测试@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 

end



def setup(&block)

    @setups.push(block)

end



Dir.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_s

end

#我加的这两行,用来测试@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

}.call



Dir.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

  end

end

  

 

附:关于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 的书评

你可能感兴趣的:(Ruby)