ボクココ

個人開発に関するテックブログ

コードを記述するコード

引き続き「メタプログラミングRuby」の学習継続。
最近はクイズを答えを見ないで自分で書いた後に比較するようにしている。何気全く同じコードがかけていたときはかなりうれしいw

今日はクラス拡張ミックスインについて。
クラスの中でモジュールをインクルードし、そのクラスのクラスメソッドやインスタンスメソッドをモジュール内で定義しちゃおう。そしてさらに動的にメソッドを作ってより柔軟にメソッドを使えるようにするという話。

attr_checked というクラスメソッドを用意。これは引数で指定したパラメータをクラスのインスタンスメソッドとしてもっておいてそれをセッターとして代入するときに検証してくれるもの。


1 require 'test/unit'
2
3 module CheckedAttributes
4 def self.included(base)
5 base.extend(ClassMethods) 6 end
7 module ClassMethods
8 def attr_checked(attribute, &validate)
9 define_method "#{attribute}" do
10 instance_variable_get "@#{attribute}"
11 end
12 define_method "#{attribute}=" do |value|
13 raise 'Invalid attribute' unless validate.call(value)
14 instance_variable_set("@#{attribute}", value)
15 end
16 end
17 end
18 end
19
20 class Person
21 include CheckedAttributes
22 attr_checked :age do |v|
23 v >= 18
24 end
25 end
26
27 class TestCheckedAttribute < Test::Unit::TestCase
28 def setup
29 @bob = Person.new
30 end
31
32 def test_accepts_valid_values
33 @bob.age = 20
34 assert_equal 20, @bob.age
35 end
36
37 def test_refuses_nil_values
38 assert_raises RuntimeError, 'Invalid attribute' do
39 @bob.age = 17
40 end
41 end
42
43 end
44

※ちなみにソースは全てGithubに置きました。
https://github.com/honmadayo/other/tree/master/metaprogrammingruby

ふつくしい。。
evalで強引にメソッド動的定義したらclass_eval&define_methodでかっこよく安全にし、どのクラスでも使えるようにClass クラスに追加し、最後にinclude指定したクラス内のみ適用するコードを書いた。


メタプログラミングマジック!

Win32OLE が素晴らしい

WindowsのアプリをRubyで自動で動かしちゃおう、というもの。
VBAみたいなよくわからない言語を使うより、使いなれたRubyでラップしてくれたものを使ってみましょう。RubyでやればWindowsオートメーションも楽しくなること間違いなし。

Memo:

require 'win32ole'
ie = WIN32OLE.new('InternetExplorer.Application')

作成するオブジェクト ProgID
Microsoft Internet Explorer InternetExplorer.Application
Microsoft Excel Excel.Application
Microsoft Outlook Outlook.Application
Microsoft Word Word.Application|

作ったオブジェクト .ole_methods
でメソッド一覧が見れる

よさげなメソッドがあった場合の調査:
  http://jp.rubyist.net/magazine/?0008-Win32OLE
  のサンプルプログラムを自分用に拡張

  https://github.com/honmadayo/other/blob/master/win32ole/olenav.rb
  ex)
  ruby olenav.rb ie Navigate

後は実践あるのみ!!
作ったコードは随時Githubに上げていこうと思います。

オレオレ attr_accessor

なんとなーく クラスマクロを理解したので、その代表格であるattr_accessor を自分で実装してみた。


1 class MyClass
2 p self # -> MyClass
3 def self.my_accessor name
4 p self #-> MyClass
5 define_method "#{name}=" do |v|
6 p self #-> #
7 @name = v
8 end
9 define_method "#{name}" do
10 @name
11 end
12 end
13
14 def test
15 p self #-> #
16 end
17 my_accessor :mine
18 end
19
20 my = MyClass.new
21 my.mine = "hoge"
22 p my.mine

  • > "hoge"

このself 大事だね!selfがクラスなのか、オブジェクトなのかを意識しておかないとバグが間違いなく発生するw
今回はdefine_methodで self, を使ってないからオブジェクトのインスタンスメソッドになってうまく動作してくれる。
うんうん。我ながら初めてメタプログラミングをやった気持ちになれた。 楽しいぞ

instance_eval と class_eval

にしても難解なRuby。これを理解するのに本を何度も読み直さないとわからない。
自分なりのメモなので間違っているかもしれませんがその時は指摘してくれるとうれしいです。

instance_eval


obj.instance_eval do
#そのオブジェクトのクラス定義のコンテキストで実行したいものを書く。
 #ここに書けばクラス定義内で使われているインスタンス変数も使えちゃう!
 #このブロックの外側に定義されている変数も使うことができる。
end

class_eval


klass.class_eval do
#この中はクラスを再オープンするのと似ている。
#インスタンスメソッドを定義したりできる。
#これもブロック外側の変数を使える
end

instance_eval, class_eval共に
「self (オブジェクトなりクラスなり)に変更を加える。」
でもclass_eval には
「クラス全体にも変更を加える」
という意味がある。
この違いで使い分けるらしい。



さて次は最も意味不明な「特異メソッド、特異クラス」だ。。

後記

なんとinstance_evalにもメソッド定義ができるらしい。 その時はそのメソッドはオブジェクトの特異メソッドとなる。

呼び出し可能オブジェクト

なんかの処理をまとめるとき、通常はdef を使いますね。
ですがdef はdefの外側にある変数を読み込めません。(スコープ)
なのでlambda を使ってオブジェクト上のコンテキストで読み込めるようにするのです。


1 b = 1
2 def add a
3 puts a + b
4 end
5
6 add 3

  • >`add': undefined local variable or method `b' for main:Object (NameError)

もちろんbはdefの外側なのでエラー。グローバル変数を使うしかないのか・・!?
いや。


8 b = 1
9 bl = lambda {|a|
10 puts a + b
11 }
12
13 bl.call(3)

  • > 4

これでいけるんです! 定義されたスコープ内で評価される。クロージャっていうらしいよ。
JavaScriptだと function ってやってもスコープ外の変数アクセスできちゃうよね。そこら辺の言語の違いをかんがえながらやると面白い。

つかなんでこれを「クロージャ」っていうんだ?w

スコープとクラスメソッドの疑問

メタプログラミングRubyでフラットスコープについて学んだ。
でもこれから一つの疑問が生じた。


1 my_var = 1 2 puts "myvar:#{my_var} was called at the top level" 3
4 MyClass = Class.new do
5 my_var += 1
6 puts "myvar:#{my_var} was called at the class"
7
8 define_method :my_method do
9 my_var += 1
10 puts "myvar:#{my_var} was called at the method"
11 end
12 end
13
14 my = MyClass.new
15 my.my_method
16
17 puts "------------"
18
19 @@my_var = 1
20 puts "myvar:#{@@my_var} was called at the top level"
21 class MyClass2
22 @@my_var += 1
23 puts "myvar:#{@@my_var} was called at the class"
24
25 def my_method
26 @@my_var += 1
27 puts "myvar:#{@@my_var} was called at the method"
28 end
29
30 end
31
32 my2 = MyClass2.new
33 my2.my_method
この二つのコードは同じ出力をする。

1 myvar:1 was called at the top level
2 myvar:2 was called at the class
3 myvar:3 was called at the method
4 ------------
5 myvar:1 was called at the top level
6 myvar:2 was called at the class
7 myvar:3 was called at the method

違いはなんだろうなぁ。


後記

define_methodした方は、その方法で定義したメソッドないだけでスコープを共有することができる。
クラスメソッドは全てのメソッドで共有してしまう。
このリスクの差のようです。

Ruby method_missing

引き続き前回の続き。。

前回のと同様に method_missing を実現するとどうなるだろう?


45 class Hoge3
46 def method_missing(name, *args)
47 if name =~ /hoge/
48 p "#{name} was called..."
49 p "Whohooo!"
50 else
51 super
52 end
53 end
54 end
55
56 hoge3 = Hoge3.new
57 (1..3).each do |method|
58 hoge3.send("hoge#{method}")
59 end

こっちのほうが直観的でわかりやすい気もする。
メソッドが定義されていない場合にmethod_missingが呼ばれるので、それを頭の中でイメージしながらやれば、define_methodと同じように実現できる。
だがこっちはメソッド探索をいちいち全部してから呼ばれるため、遅い。 
その点を注意しながらやっていく必要がある。

ちなみにActiveRecordの場合はmethod_missingを呼び出すのは一回だけにして、次からはmethod_missingの中でdefine_methodを定義してそれを呼ぶようにしてるらしい。 さすがだぜ・・。

Ruby 動的ディスパッチ

今日は基礎に戻ってRubyの学習。動的ディスパッチに関してメモ。

動的ディスパッチ

実行時に呼ぶメソッドを柔軟に変えたいときとかに send() を使う。
これが中々面白いのでいろいろ実験してた。
Test:Unitとか基本的にはメソッド名の先頭にtest_ をつけているが、その理由もわかった。 例をみるとわかりやすい。

1 class Hoge
2 def hoge1
3 p "aiueo"
4 end
5
6 def hoge2
7 p "kakikukeko"
8 end
9
10 def hoge3
11 p "sasisuseso"
12 end
13 end
14
15 hoge = Hoge.new
16
17 hoge.public_methods.delete_if{|method_name| method_name !~ /hoge/}.each do |method|
18 hoge.send(method)
19 end
こんな感じでpublic_methodsを呼び出すとスーパークラスとかにあるメソッドも全部とってきちゃうから限定してあげるために先頭にtestとかhoge とかつけてあげる模様。
すると

1 "aiueo"
2 "kakikukeko"
3 "sasisuseso"

という出力が得られる。
こういう時ってsendを使わないと確かに普通のメソッド呼び出しじゃ実現できないっすね!
このような手法をパターンディスパッチとも呼ぶらしい。
今日はこの本を読みながらやってる。難しいけど中級Rubyプログラマなら読む価値ありです。
[rakuten:neowing-r:10723263:detail]