吃了大力丸的Ruby 2

来源:互联网 发布:安堂机器人 知乎 编辑:程序博客网 时间:2024/05/17 00:13
Why's Lucky Stiff  上看来的。俺只是搬运工。能读原文的老大们不用往下看了。

嗯,假如一个类里有个实例方法。我们希望这个方法只运行一次。”切,我还以为是抢鸡蛋呢“,熟读铁撬书的老大们开始嗤之以鼻,”不就是第391页里Tadayoshi Funaba的once么?就连上一篇《吃了大力丸的Ruby》也有类似的实现”:
01: def once(*ids)
02: for id in ids
03: module_eval <<- "end;"
04: alias_method :__#{id.to_i}__, :#{id.to_s}
05: private :__#{id.to_i}__
06: def #{id.to_s}(*args, &block)
07: (@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0]
08: end
09: end;
10: end
11: end
 

那有没有其它的方法,绕开这种meta-programming的常见技巧呢?于是__Why老大提出了下面这个方法:


01: class Trial
02: def run_me
03: def self.run_me; raise Exception, "NO MORE." end
04: puts "Your trial period has ended."
05: end
06: end
07:
08: t = Trial.new
09: t.run_me
10: #=> Your trial period has ended.
11: t.run_me
12: #=> (trial):3:in `run_me': NO MORE. (Exception)
注意03行。在方法run_me()里,我们重新定义了run_me()。因为定义函数也是动态执行,当run_me()第一次运行时,03行被执行,导致新的run_me()被定义。接着04行被执行,打印出"Your trial period has ended"。函数run_me()执行完毕后,新定义取代老定义,再执行就得到第12行的结果了。注意03行用了self.run_me。这样run_me()在instance的范畴内被改写,所以我们可以不断创建新的Trail实例,里面的run_me()可以运行至少一次。如果去掉self,run_me()会在class级别被重写。导致run_me()只能被第一个Trial的实例执行一次。

从上面我们还可以印证一个细节:Ruby里的def..定义的方法的scope和执行这个定义的方法无关。执行定义的方法和被定义的方法的scope只取决于它们的context. 这点和JavaScript或Python都不一样。下面一段Ruby代码可为例证:
01: def foo
02: def bar
03: puts "bar"
04: end
05: puts "foo"
06: end
07:
08: bar
09: #=>NameError: undefined local variable or method `bar'
10: foo
11: #=>foo
12: bar
13: #=>bar

这个技术自然可以应用到记忆优化上:

01: class Hit
02:    def initialize(ip)
03:     @ip = ip
04:    end
05:   def country
06:      def self.country; @country end
07:     @country = `geoiplookup #{@ip}`.chomp.gsub(/^GeoIP Country Edition: /,"")
08:    end
09: end

后来有人评论道,把被记忆的数据转换成accessor速度更快。我一般对这种语言级别的优化不感冒。没有测量的情况下,再怎么也轮不到这种局部微调啊。不过考虑到Ruby现在的速度真的有点慢(那个soap4r简直要把人气死),还是值得提一下:
1:def country
2:   class << self ; attr_reader:countryend
3:     @country = `geoiplookup #{@ip}`.
4:     chomp.gsub(/^GeoIP Country Edition: /,"")
5: end

为什么用attr_reader就快呢?因为生成的attr_reader被放在了包含它的类的解析树之外。用它时省去了动态查找的时间:
01: class X
02: attr_reader :x
03: def y; @y; end
04: end
05: [[:class,
06: :X,
07: :Object,
08: [:defn, :x, [:ivar, :@x]],
09: [:defn, :y, [:scope, [:block, [:args], [:ivar, :@y]]]]]]

运用自我更新的方法,还可以实现无需对象的诸如有限自动机或状态模式。是的,我没有胡言乱语。不用对象,一样可以实现状态模式。具体的实现和背后的理论,就留待下一片大力丸了。性急的老大们可以到这里看例子。
原创粉丝点击