JSHashその2
4月のRubyist九州にて小ネタとして出したもの。以前のJSHashのすこし改良版。id:authorNariさん、遅くなってごめんなさい。
class BlankSlate instance_methods.each { |m| undef_method m unless m =~ /^__/ } end class JSHash < BlankSlate def initialize(org) @hash = org.inject({}) {|memo,i| k,v = i # SymbolはJSに合わせてStringに変換 memo[ (k.kind_of? Symbol) ? k.to_s : k ] = v memo } end def method_missing(sym, *args) name = sym.to_s if name =~ /=$/ name = name[0..-2] val = args[0] if val.respond_to?(:call) && name[/^[_a-z][^ ]*$/] # evalで登録するので、識別子になれないキーはメソッド定義しない eval %Q{ def #{name}(*_args) @hash["#{name}"].call(self,*_args) end } end @hash[name] = val # 代入メソッドで帰る値はこの返り値ではなくて言語仕様で決まっている return val else return @hash[sym] || @hash[name] end end def inspect return @hash.inspect end def [](key) return @hash[key] end def []=(key, value) return @hash[key] = value end # 好みの問題 def each @hash.each {|i| yield i[0] } end def size return @hash.size end end class Hash def to_jsh return JSHash.new(self) end end ################################################## # 使い方サンプル alias function lambda a = { :symbol1 => "Nishi-kiwa", "string1" => "Higashi-kiwa", :symbol2 => 1234678, "string2 key" => "value!", 8888 => 9999 } b = a.to_jsh # プロパティでアクセスできます puts "#property access" puts b.symbol1, b.string1, b.symbol2, b["string2 key"], b[8888] puts "size = #{b.size}" # 変更できます puts "#overwrite" b.symbol1 = "object!" b.string1 = "Iwahana" b["string2 key"] = 12345678 b[8888] = nil puts b.symbol1, b.string1, b.symbol2, b["string2 key"], b[8888] puts "size = #{b.size}" # toString のようなもの puts "#toString, inspection" puts b.inspect # eachで総当りのアクセスも可能 puts "#for in" b.each {|i| puts "key:#{i} value:#{b[i]}" } # function はちょっと苦しい puts "#JS Object1" b.aaa = "lambda" b.hello = function {|this,i| puts "Hello #{i} #{this.aaa}" } b.hello("JavaScript") # あるオブジェクトに所属しているメソッドを受け渡せる puts "#JS Object2" c = {}.to_jsh c.aaa = "function" c.hello = b["hello"] # b.hello はムリ c.hello("Ruby")
変更点は
- JSHashのコンストラクタでSymbol→文字列の変換
- メソッドの定義をevalに
- 代入メソッドの返り値を言語仕様通りに
ぐらい。
前回はClassのオブジェクトで無理矢理定義させていたのだけども、やっぱりインスタンスごとに関数を定義した方が自然だろうということで、特異メソッドをeval以外で登録する方法を少し探してみた。でも結局、メソッド定義のたびにModule作ってincludeする方法しか思いつかなかったので、素直にevalで定義。どうせevalで定義できないメソッドは呼べないので、今回はとりあえずこれで納得。
似たようなHashでない入れ物の仕組みとしては、添付ライブラリの ostruct がある。あと、組込みクラスのStructも手軽な入れ物として使える。
一方でJSHashでやりたかったことは、JavaScriptのオブジェクトのような「入れ物とメソッドの緩い関係」。メソッドの実装を別のオブジェクトと共有できるというのは、3年くらい前に新しいブロック記法がruby-talkで話題になっていたときにもいろいろ出てきた。RubyではUnboundMethodを使えば無理矢理メソッドをクラスから引っ張り出すことが出来るのだけども、bindできるインスタンスに制限があったり、匿名関数と自由に往復できないなど、普段のプログラミングの道具として使うものでは無い。C#のdelegateもちょっとちがう。JavaScriptは柔軟にできる反面、効率が悪かったり慣れないとthisで指す先が誰なのか分からなくなるなど、困った点も多いのだけども、そういう緩い感じが心地よいと思うこのごろなのであります。
Rubyist九州会議 2008
があります。詳細:日本Rubyの会 公式Wiki - Rubyist九州会議 2008 in 福岡
最近は EventMachine を調査していて、間に合いそうであればそれについて何か話そうかと思っていたのだけども、やっぱり時間がなさそうなので今回は見送り。。。