RUBY元编程学习之”编写你的第一种领域专属语言“
今天又学了一会RUBY的闭包,主要是看《RUBY元编程(metapromgramming ruby)》一书:
http://book.douban.com/subject/4086938/
第三章闭包结尾的守关BOSS是一道题:编写你的第一种领域专属语言。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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. 对上面这段测试文件运行,得到如下的输出
1 2 3 4 5 6 | Setting up sky Setting up mountains Alert: the sky is falling Setting up sky Setting up mountains Alert: It's getting closer |
原书作者的给出的答案如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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的方式做了下面的这个测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 这两个全局变量也去掉了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | #--- # 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 的书评
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具