Rubyのメタプログラミングの書籍写経メモ
某書を写経したメモ
メタプログラミングとは
- コードを記述するコードを記述することである
- 言語要素を実行時に操作するコードを記述することである
オブジェクトモデル
- ネタバレも含まれているが・・
- インスタンスにはインスタンス変数と親クラスと特異クラスへの参照があるのみ、メソッドは親クラス、特異クラスに存在する
- クラスにはクラス変数とクラスインスタンス変数とインスタンスメソッドとスーパークラス、特異クラスへの参照がある
- クラスメソッドは特異クラス内にインスタンスメソッドとして存在する
- ネームスペース
- 定数
-
module A module B C = 'A_B_C' end p B::C # => "A_B_C" end p A::B::C # => "A_B_C"
-
B = 'B' module A B = 'A_B' p B # => "A_B" p ::B # => "B" end
-
# レシーバ内部の定数一覧 A::B.constants # => [:C] # トップレベルの定数一覧 Module.constants # => [.....]
-
# パスの取得もできる module A class B module C p Module.nesting # => [A::B::C, A::B, A] end end end
- load は定数が既存の定数を汚染する可能性がある そういう場合には load(path, true) を使うと無名モジュールでラッピングし解釈、その後破棄してくれる
- require は定数等を取り込むためのものである為、そういう心配も機能も無い
-
- メソッド探索
- メソット呼び出し
- メソッド探索を行う
- 継承チェーン
- Rubyがクラスに入り、メソッドを見つけるまで継承チェーンを登る
- object.singleton_class.ancestors
- include module
- prepend module
- 継承チェーンに既に存在しているモジュールはインクルードされない。無視される
-
module M1 end p M1.ancestors #=> [M1] module M2 include M1 end p M1.ancestors #=> [M1] p M2.ancestors #=> [M2, M1] module M3 prepend M1 include M2 end p M1.ancestors #=> [M1] p M2.ancestors #=> [M2, M1] p M3.ancestors #=> [M1, M3, M2] # include M2 しようとした時に「あ、M1はもう居るじゃん、君は無しね」となる # まぁ、[M1, M3, M2, M1]で前方優先でユニークにした、と考えてもいいのかもしれない
- KernelはObjectがインクルードしているので全てのオブジェクト内で使用できる
- 例えばこいつら
-
Kernel.private_instance_methods.grep(/\Ap/) # => [:printf, :print, :putc, :puts, :p, :proc]
- kernelにメソッドを追加すれば、同じようにどこからでも使えるカーネルメソッドが作れるということ
- 継承チェーン
- メソッド呼び出しには self が必要
- レシーバがselfになる
- Rubyのコードはオブジェクト(カレントオブジェクト)内で実行される。それはレシーバであり、self である
- インスタンス変数も、レシーバ無しで呼び出されるメソッドもselfのものが対象である
- 常にselfを意識すべきである。最後にレシーバとなったオブジェクトを追いかければよい
- メソッド探索は毎回メソッド探索チェーンを下から探索する。近くに見つかったからと言ってそれを呼び出すわけではない
-
module A def moko hage end def hage 'A::HAGE' end end module B def hage 'B::HAGE' end end class C include A include B end p C.new.moko # => "B::HAGE"
- トップレベルコンテキスト
- Rubyのコードはオブジェクト内で実行されるなら、例えばirbを起動した直後、クラス定義でもモジュール定義でもメソッド定義でもない、今はどこに居るのだろうか?
- ご存知、mainオブジェクト内です
- Rubyのコードはオブジェクト内で実行されるなら、例えばirbを起動した直後、クラス定義でもモジュール定義でもメソッド定義でもない、今はどこに居るのだろうか?
- クラス定義、モジュール定義内では self は自分そのものとなる
- 例外 Refinements
- refine と using を覚えてればいいんじゃね
-
module R refine String do def hage 'hage-!' end end end module M # このモジュール内でのみ、refineが有効になる using R p ''.hage # => "hage-!" end
- メソッド探索を行う
- メソット呼び出し
- 定数
メソッドとはいったい・・・
- ボイラープレートメソッド
- コンパイラを満足させる為だけに存在するメソッド
- def get_a.. def get_b.. def get_c.. def get_d..
- Rubyでは静的言語では実現不可能な技法でこういうものの回避が可能
- メソッドを動的に呼び出す
- 動的ディスパッチとも呼ぶ
-
object.send(:method_name, '第1引数', '第2引数', ...)
- publicメソッドも呼び出せるので、配慮したいなら object.publice_send もある
- ていうかprivateメソッドを呼びだしたいからsendを使ってるんだけども
- メソッドを動的に定義する
- Module#define_method(:method_name) { block }
- selfのインスタンスメソッドになる
- 動的メソッドとも呼ぶ
- method_missing
- Rubyではメソッド呼び出しを制限されない。存在しないメソッドも呼び出せるということ
- メソッド探索にてメソッドを発見できなかった場合、Rubyはself#method_missing(:method_name) を呼び出す。
- method_missingはBasicObjectに定義されており、全てのオブジェクトから呼び出す事が確定している
- objectに「このメソッドは君の継承ツリーを全部探したけど無かったよ、何か言いたい事はあるかい?」と囁く
-
send(:method_missing, :method_name) # BasicObjectが反応する # => NoMethodError: undefined method 'method_name' for main:Object
-
- methiod_missing のオーバーライド
- 自分でmethod_missingを呼び出す理由はおそらくないだろうが、継承チェーンの何処かでそのメソッドを定義し、BasicObjectによりNoMethodErrorがraiseされる代わりに何か他の処理を行わせる事ができる
-
class Moko def method_missing(method_name) method_name.to_s end end p Moko.new.hage # => "hage"
- ゴーストメソッドとも呼ぶ
- もちろん Object#methods の一覧には登場してくれない
- タイプミス等で method_missing が永久ループし、スタックを食いつぶす事もよくある
- 原因に気づきにくいバグになりやすいので method_missing は注意深く使わなければならない
- 動的プロキシ
- ゴーストメソッドを補足して他のオブジェクトに転送するオブジェクト
- respond_to_missing?
- ゴーストメソッドを respond_to? するとfalseが返ってくる。確かにそんなメソッドは存在しないからである
-
class Moko def method_missing(method_name) method_name.to_s end end moko = Moko.new p moko.hage # => "hage" p moko.respond_to?(:hage) # => false # 「そんなメソッドは無いです」
- respond_to? にゴーストメソッドを認識させる仕組み
- respond_to? は respond_to_missing? を呼び出して要るのでオーバーライドする
- 以前は respond_to? を直接オーバーライドしていたが、最近では respond_to_missing? をオーバーライドする方に変わりました
- ちなみに継承チェーンを登り、Object#respond_to_missing? で false が返ってくる
-
class Moko def method_missing(method_name) method_name.to_s end def respond_to_missing?(method_name, private_include = false) [:hage, :aho].include?(method_name) end end moko = Moko.new p moko.hage # => "hage" p moko.respond_to?(:hage) # => true
- respond_to? は respond_to_missing? を呼び出して要るのでオーバーライドする
- ブランクスレート
- method_missingで存在しないメソッドをゴーストメソッド化したと思いきや、実際に同名のメソッドがObjectに存在した、ような事もあるかもしれない
- つまり、Object ではなく BasicObject を継承するだけで解決するならそれでいいだろう
- しかし、残したいメソッドも有るかもしれない、なら別の方法を探す
- メソッド削除したブランクスレートクラスを自力で用意するのもいいかもしれない
-
class BlankSlate class << self def hide(method_name) return if method_name.match?(/\A(__|methods|object_id)/) undef_method method_name end end instance_methods.each { |m| hide(m.to_s) } end p BlankSlate.new.methods # => [:object_id, :methods, :__send__, :__id__]
- ここまでやって、ゴーストメソッド(method_missing)と動的メソッド(define_method)、どっちを使うの?
- 可能であれば動的メソッドを使い、仕方がなければゴーストメソッドを使う
- ついでに const_missing
- 定数が見つからない時に呼び出される
-
class Moko class << self def const_missing(const_name) const_name.to_s end end def hage HAGE end end p Moko.new.hage # => "HAGE"
ブロック
- ブロックは便利なものだが、スコープを制御するのに強力なツール
- ブロックの基本
- ブロックは波カッコまたはdo..endで定義する
- ブロックを定義できるのはメソッド定義時のみ、ブロックはメソッドに渡され、メソッドはyieldでブロックをコールバックする
- ブロックには引数を渡せる
- メソッドのように最終行を評価した値を返す
- メソッド内部ではblock_given?を使ってブロックの有無を確認できる。ブロックが渡されていないのにyieldするとエラーになる
-
class Moko def hage(a = 1, b = 10) if block_given? == false p 'Give me block' else yield(a * 2, b * 2) end end end moko = Moko.new moko.hage # => "Give me block" moko.hage(1, 5) do |a, b| p (a..b).to_a end # => [2, 3, 4, 5, 6, 7, 8, 9, 10]
- 次のような使い方(コネクション#closeが呼び出される事が確定している)なんて便利です
-
def connection_exec_with_close(conn) yield ensure conn.close end connection = Connection.open connection_exec_with_close(conn) do |connection| p conn.gets end
- ブロックはどこにでも存在できるものではない
- ローカル変数、インスタンス変数、self といった “環境” が必要になる
- これらはオブジェクトに紐付けられた名前の事で、”束縛” とも呼ばれる
- ブロックとは “コード” “束縛の集まり” が含まれ、実行の準備をするもの
- ブロックが取得する束縛は “その場所にある束縛” である
- 違う言い方をすると、ブロックから参照できるスコープは、ブロックを定義した場所のスコープである
-
def hage hage = 'hage2' yield end hage = 'hage1' hage { p hage } # => "hage1"
- メソッド内で定義された束縛はブロックからは見えない
-
def hage hage = 'hage2' yield end hage { p hage } # => 'hage': no block given (yield) (LocalJumpError) # NameErrorじゃないんだ・・まぁいいか
- ブロックはクロージャだ
- ブロックは定義された時点のスコープを参照する、と覚えておけばおk
- スコープ
- 新しいスコープに入ると束縛は新しい束縛と書き換えられる
- 変数の巻き上げ(ホイスティング)
- この本に書いてるのかよくわからないがホイストは行われる
-
moko1 = 1 local_variables.each do |lv| p "#{lv}, #{binding.local_variable_get(lv).inspect}" end # => "moko1, 1" # => "moko2, nil" moko2 = 2 local_variables.each do |lv| p "#{lv}, #{binding.local_variable_get(lv).inspect}" end # => "moko1, 1" # => "moko2, 2"
- スコープゲート
- スコープが切り替わり、新しいスコープをオープンする場所は3つある
- classキーワード moduleキーワード dedキーワード である
- この3つの境界線は class module def という印がついている
- この4つのキー輪d-はスコープゲートとして振る舞う
- このゲートを横断するように変数を渡すにはどうしよう
- スコープが切り替わり、新しいスコープをオープンする場所は3つある
- スコープのフラット化
- そのうち、スコープを超えて変数を渡したいような場面に出くわすかもしれない
-
hage = 'Hage-!' class Moko p hage # ここで使いたい # => undefined local variable or method 'hage' end
- スコープゲートの守りは固く、入ったり抜けたりした瞬間にローカル変数は消え去ってしまう
- では、上記のようにローカル変数がスコープゲート通り抜ける事は不可能なのだろうか?
- classをスコープゲートではない何かに変える事はできる。classキーワードではなく、メソッド呼び出しにてクラスを作成、ローカル変数はクロージャに包み込んでブロック内部で参照できる
- クラスを作成するメソッドとは何か、Class.new である
-
hage = 'Hage-!' Moko = Class.new do p hage # => "Hage-!" end p Moko.class # => Class
- んでは、def のスコープゲートを超えるにはどうすればよいか?
- 同じようにメソッド定義をメソッドにて行う、def キーワードを Module#defime_method にて行えばおk
-
hage = 'Hage-!' Moko = Class.new do define_method(:hage) do hage end end p Moko.new.hage # => "Hage-!"
- ※ module は Module.new
- スコープゲートをメソッド呼び出しに書き換えると他のスコープの変数が見えるようになる
- 技術的には入れ子構造のレキシカルスコープと呼ばれる
- 「スコープのフラット化」とみんなは呼んでいる
- この魔術をフラットスコープと言う
- スコープの共有
- フラットスコープが分かればスコープでやりたいことはだいたい出来るようになる
- 複数のメソッドである変数を共有したいなら全てのメソッドをお案じフラットスコープに定義すればよい
-
def define_methods shared_variable = 0 Kernel.send(:define_method, :counter) do shared_variable end Kernel.send(:define_method, :inc) do shared_variable += 1 end end define_methods p counter # => 0 p inc # => 1 p counter # => 1
- 変数を共有するこのスマートな方法は “共有スコープ” と呼ばれている
- 共有スコープは実際にはあまり使われない。がこれは強力な魔術である
- スコープゲート、フラットスコープ、共有スコープを組み合わせればスコープを自由に捻じ曲げ、好きな場所から好きな変数を参照する事ができる
- コードを束縛を好きなように組み合わせるもう一つの方法
- BasicObject#instance_eval に 渡したブロックは
- selfがレシーバに変更され
- カレントクラスがレシーバの特異クラスに変更され(後述)
- てから実行される
- privateメソッドやインスタンス変数へアクセスし放題である
- もちろんブロック定義時のクロージャの束縛にもアクセス可能である
- 強力すぎなんじゃないのコレ?
- instance_eval に渡したブロックのコードを “コンテキスト探査機” とも呼ぶ。オブジェクトの内部を解析し、そこで何かを実行することが可能だからだ
-
class Moko def initialize @hage = true end private def not_hage @hage = false end end moko = Moko.new mokohage = 'mokohage' moko.instance_eval do p mokohage # => "mokohage" p self # => #<Moko:0x00007fe4649dd4b0 @hage=true> p @hage # => true not_hage p @hage # => false end
- BasicObject#instance_exec instance_evalの双子の兄弟 こちらは引数を渡す事ができる。余り使う場面は多くはないが、必要になる時もあるだろう
- BasicObject#instance_eval に 渡したブロックは
- カプセル化の破壊
- オブジェクト指向においてカプセル化はオブジェクトのデータを隠蔽・保護するものであった筈だがそのバリアは容易に破壊できる。プライベートなデータなどない。暗黒面に落ちぬような強い心が必要となる
- クリーンルーム
- ブロックを評価するためだけにオブジェクトを作成する場合がある。このようなオブジェクトはクリーンルームと呼ばれる
-
clean_room = CleanRoom.new clean_room.instance_eval do # オブジェクトに影響を及ぼすような処理 end
- クリーンルームとして使うには BasicObject が最適だろう。メソッドがほとんど存在しないブランクスレートだからである
- BasicObjaectはこれ以上無いクリーンルームである。例えばStringにアクセスする時でさえ ::String のように指定しなければならない程である
- 呼び出し可能オブジェクト
- ブロックは大家族の一員に過ぎない
- Proc オブジェクト
- “Rubyは全てがオブジェクト” なんて言うかもしれないが、ブロックはオブジェクトではない。
- ブロックをオブジェクト化したものがProcなのである
- Proc.new にブロックを渡し、後で call すれば実行できる
- Kernel.proc Kernel.lambda でもProcオブジェクトを生成できる
- Proc.new と Kernel.proc は ほぼ同じ動きをする、でほぼ間違いない
- kernel.lambda と kernel.proc は わざわざ違う名前が付いてるくらいなので違いがある。でも微妙な違いなので好きな方を使って良い
- Kernel.lambda { |a| p a } は -> (a) { p a } というような矢印でも同様にラムダを生成できる
- & 修飾
- ブロックからProcオブジェクトを生成するもう一つの方法
- ブロックはメソッドに渡す無名引数のようなものだ
- メソッドの中でyieldによって実行されるが、渡されたブロックに名前があるわけではない
- ブロックは一つしか渡されない為、名前が付いている必要も無かったわけである
- しかし
- 他のメソッドにブロックを渡したい
- ブロックをProcに変換したい
- ような時にはブロック指して「このブロックを使用したい」という必要がある
- そのためには、そのブロックを指し示す名前が必要だ
- ブロックに名前を束縛するにはどうしたらいいのか?
- メソッドの引数に特別な引数を追加すればいい。引数の末尾に置いて名前の前に & を付ける
- 渡したブロックをProcオブジェクトとして受け取る例
-
def moko(a, &block) p block p block.call(1) end moko(1) do |a| p a end # => #<Proc:0x00007fc679185dc0@moko1.rb:6> #=> 1
- 逆も可能か?もちろんです この場合も & を使いますよ
- 渡したProcオブジェクトをブロックとして受け取る例
-
def moko(a) p a + yield end my_proc = Proc.new { 1 } moko(1, &my_proc)
- つまり
- & を付けて受け取るとブロックはProcオブジェクトに変更される
- & を付けて呼び出すとProcオブジェクトはブロックには変換される
- Proc vs lambda
- どう違う
- どっちも Procオブジェクトである
- Proc#lambda? でラムダなのかどうかは判定可能
- その違いは「ほぼ同じなんだけど・・」である
- 特殊なケースがたくさんあり、明確な根拠のない違いもたくさんある
- たぶん殆どの人が理解してない / 理解する必要もない
- その中でも影響が大きそうな違いだけは把握しておく
- return
- lambda のreturnは単にlambdaから戻るだけ (callした行にreturnする)
- Proc のreturnはProcが定義されたスコープから戻る (定義した行からreturnしようとする)
-
def moko(lmd) p 'moko start' p lmd.call p 'moko end' end lmd = lambda { return 'hage-!' } moko(lmd) # => "moko start" # => "hage-!" # => "moko end"
- proc のパターン トップレベルからはreturnできない。落ちる
-
def moko(prc) p 'moko start' p prc.call p 'moko end' end prc = proc { return 'hage-!' } moko(prc) # => LocalJumpError: unexpected return
- proc のパターン2
-
def moko p 'moko start' prc = proc { return 'hage-!' } p prc.call p 'moko end' # この行は評価されない end p moko # => "moko start" # => "hage-!"
- 項数の違い
- 変数の数のチェック方法が違う
- これまた特殊なケースがたくさんあるが、一般的に lambdaの方がProcより変数の数に厳しい
-
proc { |a, b| p a, b }.call(1) # => 1 # => nil proc { |a, b| p a, b }.call(1, 2, 3) # => 1 # => 2 -> (a, b) { p a, b }.call(1) # => wrong number of arguments (given 1, expected 2) (ArgumentError) -> (a, b) { p a, b }.call(1, 2 ,3) # => wrong number of arguments (given 3, expected 2) (ArgumentError)
- 変数の数のチェック方法が違う
- 一般的に言えば、lambdaの方がメソッドに似ているので直感的な動作に近いと言われ、
- Ruby使いからは “どうしてもProcが必要な時以外は” lambdaが好まれて使われているようだ
- return
- どう違う
- Methodオブジェクト
- (゚Д゚)ハァ? 呼び出し可能?いつも呼び出してるけど?
- 評価した結果を他のものに渡したりはしてるけど、評価するメソッドそのものを誰かに渡したり、してませんよね?
- Object#method で、メソッドをそのものをMethodオブジェクトとして取得できる
- Methodオブジェクトは proc や lambda のように callで実行できる
- Methodオブジェクトは自身が所属しているスコープで評価される (procやlambdaは自分が定義された時のスコープで評価される)
- Methodオブジェクトは自身が所属しているオブジェクトに束縛され、そのオブジェクトのスコープで評価されるのを強いられているんだ!
-
class Moko def initialize(a) @hage = a end def hage(b) p self p "Moko#hage #{@hage} #{b}" end end moko = Moko.new(true) metho = moko.method(:hage) metho.call(123) # => #<Moko:0x00007facbe0d0c10 @hage=true> # => "Moko#hage true 123"
- UnboundMethodオブジェクト
- (゚Д゚)エ? まだあんの?めんどくせえ
- 自身が所属しているオブジェクトから束縛されていないMethodオブジェクト
- もちろん評価するには評価する”環境”が必要である為、マトモにcallできない
- 生成方法 1 Method#unbind
-
class Moko def initialize(a) @a = a end def hage @a end end moko = Moko.new(123) metho = moko.method(:hage) p metho.class # => Method p metho.call # => 123 unbind_metho = moko.method(:hage).unbind p unbind_metho.class # => UnboundMethod p unbind_metho.call # => undefined method 'call' for #<UnboundMethod: Moko#hage> (NoMethodError)
- 生成方法 2 Module#instance_method
-
class Moko def initialize(a) @a = a end def hage @a end end unbind_metho = Moko.instance_method(:hage) p unbind_metho.class # => UnboundMethod p unbind_metho.call # => undefined method 'call' for #<UnboundMethod: Moko#hage> (NoMethodError)
- マトモに評価できないUnboundMethodオブジェクトを、適当なオブジェクトに束縛して真っ当なMethodオブジェクトを取得できる
- ただし、元のクラスかサブクラスにしかbindできない
- UnboundMethod#bind
-
class Moko def initialize(a) @a = a end def hage @a end end unbind_metho = Moko.instance_method(:hage) class Hage < Moko undef_method(:hage) # なんとなくundef end hage = Hage.new('HAge') binded_method = unbind_metho.bind(hage) p binded_method.call # => "HAge"
- マトモに評価できないUnboundMethodオブジェクトを、適当なクラスのインスタンスメソッドとする
- やっぱり、元のクラスかサブクラスにしかbindできない
- Module#define_method の2つめの引数に渡す
-
class Moko def hage true end end class Hage < Moko end unbind_metho = Moko.instance_method(:hage) Hage.send(:define_method, :hogehoge, unbind_metho) p Hage.new.hogehoge # => true
クラス定義
- クラス定義の事をメソッドやクラス変数などを定義する場所だと思ってるかもしれない
- 実際にはクラス定義内にはあらゆるコードを置く事ができるし、即評価され、最後の行の評価結果を戻り値をして返しもする
-
p class Moko 'Hage-!' end # => "Hage-!"
- カレントクラス
- Rubyは常に カレントオブジェクト: self を持っている
- Rubyは常に カレントクラス(もしくはモジュール)を持っている
- defキーワードでメソッドを定義する、というのはカレントクラス(もしくはモジュール)へメソッドを定義する、という事
- トップレベルではカレントクラスはObjectである。(self.class => Object) トップレベルにメソッドを定義するとObjectクラスのインスタンスメソッドになるのはこの為である
- class キーワードでクラスをオープンすると、そのクラスがカレントクラスになる
- module キーワードでモジュールをオープンすると、そのモジュールがカレントモジュールになる
- そして、メソッドの中ではカレントオブジェクトのクラスがカレントクラスになる
-
p "1 current:object: #{self} current_class:#{self.class}" class Moko p "2 current:object: #{self} current_class:#{self}" def hage p "3 current:object: #{self} current_class:#{self.class}" end p "4 current:object: #{self} current_class:#{self}" end p "5 current:object: #{self} current_class:#{self.class}" moko = Moko.new moko.hage # => "1 current:object: main current_class:Object" # => "2 current:object: Moko current_class:Moko" # => "4 current:object: Moko current_class:Moko" # => "5 current:object: main current_class:Object" # => "3 current:object: #<Moko:0x00007fd0408af170> current_class:Moko"
-
class Moko def define_metho p 'メソッドの中で def すると、カレントクラスである' p self.class p 'のインスタンスメソッドになるということ' def hage p 'Hage-!' end end end moko = Moko.new moko.define_metho moko.hage # => "メソッドの中で def すると、カレントクラスである" # => Moko # => "のインスタンスメソッドになるということ" # => "Hage-!"
- class キーワードはクラス定義をする、というよりはカレントクラスを変更するものなのだ
- module キーワードはモジュール定義をする、というよりはカレントモジュールを変更するものなのだ
- クラス名が分からない、評価時にクラスが確定する・・という状況でクラスをオープンしたい
- Module#class_eval (別名 module_eval)
- レシーバのクラスのコンテキストでブロックを評価する
- self と カレントクラスがレシーバと同値になる、という事
- また、メソッドなのでブロックのクロージャの束縛も参照できる
-
def add_method(klass) klass.class_eval do p self def hage 'Hage-!' end end end add_method(Integer) p 1.hage # => Integer # => "Hage-!"
- instance_eval に instance_exec という双子メソッドがあるように class_eval, module_eval にも class_exec, module_exec が存在する もちろん引数が渡せる出来る子
- 「クラスもオブジェクトなんだからinstance_eval出来るんじゃないのか」
- ごもっともです。
- ちゃんと説明するには特異クラスが必要になります、今は
- instance_eval は
- selfをレシーバに
- カレントクラスをレシーバの特異クラスに(後述)
- 変更してくれるんで「インスタンスをオープンする、今メソッドを定義したら特異メソッドになるな、もしくはカレントクラスは気にしねえよ」って時
- selfをレシーバに
- カレントクラスをレシーバのクラスに
- 変更してくれるんで「クラスをオープンする、今メソッドを定義したらインスタンスメソッドになるな」って時
- に使う、ような感じでイメージしておく
- instance_eval は
- クラスインスタンス変数
- Rubyは「インスタンス変数はカレントオブジェクト :self に属しているもの」と考える
- クラス定義内のメソッド定義内の self はインスタンスを指す
- その時にインスタンス変数を定義すると、インスタンスがインスタンス変数を保持する事
- アクセスできるのは持ち主、つまりオブジェクト自身のみである
- クラス定義内の self はクラスと同値
- その時にインスタンス変数を定義すると、クラスがインスタンス変数を保持する事
- クラスインスタンス変数、と呼ばれる(クラス変数とは別物)
- アクセスできるのは持ち主、つまりクラス自身のみである。自分のインスタンスやサブクラスからはアクセスできない(クラス変数とは別物やろ?)
-
class Moko @hage = 1 # <= この @hage と def initialize @hage = 2 # <= この @hage は保持される場所が違う、別物なのだ end def hage p "#{self} のインスタンス変数 #{@hage}" end p "#{self} のインスタンス変数 #{@hage}" end Moko.new.hage # => "Moko のインスタンス変数 1" # => "#<Moko:0x00007f833e110698> のインスタンス変数 2"
- クラス変数との違いとかそういうコラム
- クラス変数はサブクラスやインスタンス達からもアクセスできる
- 継承先からも簡単に値を変える事ができる、変えられてしまう危険性もある
- 正しい動作なんだけど、感覚的にクラス毎に保持してくれるような気がするお・・という残念な感じ
-
class A @@hage = true def hage @@hage end end class B < A @@hage = false end a = A.new p a.hage # => false # あ、マジで?
- Ruby使い達はクラス変数は使わずに、クラスインスタンス変数の方をよく使っている
-
class A @hage = true def self.hage # 当然、クラスメソッドからしかアクセスできない @hage end end class B < A @hage = false end p A.hage # => true p B.hage # => false
- クラス変数はサブクラスやインスタンス達からもアクセスできる
- 無名クラスが自分の名前を認識する瞬間
- まずは classキーワードを使った変哲もない例
-
Object.constants.grep(/Hage/) # => [] class Hage self end Object.constants.grep(/Hage/) # => [:Hage]
- Class.new を使って無名クラスを生成する(定数に入れた瞬間に名前が確定し、定数リストに現れるようになる)
-
Object.constants.grep(/Hoge/) # => [] hoge = Class.new # ローカル変数に一時的に入れる Object.constants.grep(/Hoge/) # => [] hoge.name # 名前はまだ無い # => nil Hoge = hoge # 定数に入れてみる Object.constants.grep(/Hoge/) # 突然の出現 # => [:Hoge] hoge.name # => "Hoge"
- 特異メソッドの導入
- Object#define_singleton_method(:method_name) {}
- def object.method_name 〜 end
- 特異メソッドの真実
- クラスは案なるオブジェクトであり、クラス名は単なる定数だった
- つまり、クラスのメソッドの呼び出しがオブジェクトのメソッドの呼び出しと全く同じだということに気づくだろう
- 1行目は変数で参照したオブジェクトのメソッドを呼び出している
- 2行目は定数で参照したクラス(オブジェクト)のメソッドを呼び出している
-
a_instance.a_intance_method ACLASS.a_class_method
- クラスメソッドはクラスの特異メソッドなのだ
- オブジェクトへの特異メソッドの定義とクラスメソッドの定義をかくすれば同じものだと気づくだろう
-
def a_object.a_intance_method end def ACLASS.a_class_method end
- つまり、特異メソッドをdefキーワードを用いて定義する方法は常にこのようになる
-
def object.method_name # 処理 end
- object の部分はオブジェクトへの参照、クラスを参照する定数、selfのいずれかが使える
- 他にもクラスメソッドを利用した区使われる魔術が存在する
- クラスマクロ
- attr_acesser の例
- Rubyのオブジェクトにはアトリビュートがない。それが欲しい時には読み取り、書き込みのリーダーセッターなメソッドを定義することだろう
- moko.hage=(true) を呼び出す際に moko.hoge = true と書ける、Rubyのパーサが書けてくれた魔法、そのメソッドをミミック(擬態)メソッドと呼びたい、とか書いてある
-
class Moko def attribute_name=(value) @attribute_name = value end def attribute_name @attribute_name end end
- このようなメソッド(この場合はアクセッサ等と言ったりする)を書いているとすぐに退屈になる、もしくは死にたくなる
- そのような時のために、Module.attr_reader, attr_writer, attr_aceesor が用意されている
- Moduleのメソッドとして定義されているので、selfがクラスでもモジュールでも使える
- このような、キーワードのように呼び出せるメソッドをクラスマクロと呼ぶ
- その実態は単なるクラスメソッドである
- attr_acesser の例
- 特異クラス
- Rubyは裏に特別なクラスを持っている
- オブジェクトの特異クラスと呼ばれるものだ(メタクラス、シングルトンクラスとの呼ばれる)
- 全てのオブジェクトは裏に得クラスを保持している(クラスオブジェクト、インスタンスオブジェクト、特異クラスオブジェクトでさえも)
- Object#classなどのメソッドは特異クラスを丁寧に隠してしまう、がそれを回避する手段もある
- classキーワードは指定したクラスのスコープに移動するものだった
- ```ruby class Moko p self #=> Moko end
- classキーワードに、特異クラスへのスコープへ移動してもらう
- 我々はついに特異クラスへの参照を手に入れた
- 知っている人はとうに知っている、「あの、クラスメソッドを定義するときに使う構文そのものじゃないか」である。その通りである
-
class << Moko p self # => #<Class:Moko> end
- まぁ、最近のRubyは Object#singleton_class を装備しているので
-
Moko.singleton_class # => #<Class:Moko> Moko.new.singleton_class # => #<Class:#<Moko:0x00007fab99012100>>
- で簡単に取得できるんですけどね
- さて、特異クラスはもちろんクラスだ。しかして特別なクラスだ
- 上記のような変な構文を用いないと見る事ができない
- インスタンスを1つしか持てない(本来の意味的に、而してシングルトンクラスと言うのはこのためである)
- クラス定義時に継承することができない
- そして、オブジェクトの特異メソッドが住まう場所だ
-
obj = '' def obj.hage true end obj.hage # => true ''.hage # => NoMethodError: undefined method 'hage' for "":String obj.singleton_methods # => [:hage] obj.singleton_class.instance_methods(false) # => [:hage]
- メソッド探索 再び
- というか、ここ の一番上にまとめてしまったのでここを見ればほとんど書いてある
- インスタンスのメソッド探索
- 特異メソッドが話題に上がるまでは(インスタンス -> 自分のクラス -> 親クラス -> Object -> (謎のKernel) -> BassicObject)とスーパークラスを駆け上りメソッドを探索する、だったと思うが
- 特異メソッド同名のインスタンスメソッドが既に存在していたらどうなる?
- 結果は下記
-
class Moko def hage p 'Moko#hage' end end moko = Moko.new def moko.hage p '#moko(singleton_class).hage' super end moko.hage # => "#moko(singleton_class).hage" # => "Moko#hage" moko.singleton_class.superclass => Moko
- 先に特異クラスに存在するメソッドを探索、superでMokoクラスの同名メソッドに駆け上る
- mokoの特異クラスのスーパークラスがMoko
- 状況証拠がありすぎる。
- インスタンスのメソッドの探索ルートは(インスタンス -> インスタンスの特異クラス -> 自分のクラス -> 親クラス -> Object -> (謎のKernel) -> BassicObject)
- 答え合わせ
-
moko.singleton_class.ancestors => [#<Class:#<Moko:0x00007fa68f867170>>, Moko, Object, Kernel, BasicObject]
- クラスメソッドの探索、そして特異クラスの継承
- もうコードで確認する。クラスメソッドのメソッド探索チェーンはどうなっている?
-
Moko.singleton_class.superclass # => #<Class:Object> Moko.singleton_class.ancestors => [#<Class:Moko>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] Moko.ancestors => [Moko, Object, Kernel, BasicObject]
- 「Mokoの特異クラスのスーパークラス」は、「MokoのスーパークラスであるObjectの特異クラス」になっている
- 「Objectの特異クラスのスーパークラス」は、「ObjectのスーパークラスであるBasicObjectの特異クラス」である
- Kernelは飛ばされたの?と思ったら、継承チェーンに Class, Module ときて Objectにぐるっと戻ってきている。
- なんでこうなってるの?
- 親クラスのクラスメソッドをサブクラスから呼び出せる
- あれ?それだけ? うーむ
- 正直、自分で継承図を作ってみないと覚えられないと思う
- クラスは案なるオブジェクトであり、クラス名は単なる定数だった
- 大統一理論
- オブジェクトは1種類しかない。それが通常のオブジェクト(インスタンス、クラスオブジェクト)、モジュールになる
- モジュールは1種類しかない。それがそれが通常のモジュール、クラス、特異クラスのいずれかになる
- メソッドは1種類しかない。メソッドはクラスはモジュール(大半はクラスの形になっている)に住んでいる
- 全てのオブジェクトは(クラスオブジェクトも含め)「本物のクラス」(メソッドを最初に探索に行くクラスの事?)を持っている。それは通常のクラスか特異クラスである
- オブジェクトの特異クラスのスーパークラスはオブジェクトのクラスである。クラスの特異クラスのスーパークラスはクラスのスーパークラスの特異クラスである
- メソッドを呼び出すときは「本物のクラス」を探索し、継承チェーンを駆け上る
- というわけで、クラスメソッドの書き方
- 1つ目の書き方はRuby使い達は否定的である。クラス名が重複するからである
- 2つ目の書き方はselfがクラス自身を指す事を上手く利用している
- 3つ目の書き方はRuby使い達は否定的である。1つ目の書き方と同じ理由である
- 4つ目の書き方はナウい。「ここから特異クラスだぞ!」と名言している。Ruby使い達は好んで使う
-
def Moko.hage end class Moko def self.hage end end class Moko def Moko.hage end end class Moko class << self def Moko.hage end end end
- class_eval とinstance_eval のおさらい
- class_eval は selfをレシーバに変え、カレントクラスをレシーバのクラスに変える
- ここでメソッドを定義すればそのクラスのインスタンスメソッドになるという事
- instance_eval は selfをレシーバに変え、カレントクラスをレシーバの特異クラスに変える
- ここでメソッドを定義すればそのオブジェクトの特異メソッドになるという事
-
class Moko end moko = Moko.new Moko.class_eval do def hage p 'Moko#hage' end end moko.instance_eval do def hage p 'moko.singleton_class.hage' super end end moko.hage # => "moko.singleton_class.hage" # => "Moko#hage"
- class_eval は selfをレシーバに変え、カレントクラスをレシーバのクラスに変える
- マクロメソッドであるアクセッサメソッドをクラスに対して、特異クラスに対して、インスタンスに対して使う
- Mokoクラス内でアクセッサを定義するとMokoインスタンス全てにおいて、アトリビュートが使えるようになる という事
-
class Moko attr_accessor :hage end moko = Moko.new moko.hage = true moko.hage # => true
- 抽象度を1つ上げると、Classクラス内でアクセッサを定義するとクラスオブジェクト全てにおいて、アトリビュートが使えるようになる という事
- これは殆どの場合、望まれた実装ではないだろう
-
class Moko end class Class attr_accessor :hage end Moko.hage = true Moko.hage # => true String.hage = 'Hage-!' String.hage # => "Hage-!"
- mokoインスタンスの特異クラス内でアクセッサを定義するとmokoインスタンスのみにおいて、アトリビュートが使えるようになる という事
- これも殆どの場合、望まれた実装ではないだろう
-
class Moko end moko1 = Moko.new moko2 = Moko.new class << moko1 attr_accessor :hage end moko1.hage = true moko1.hage # => true moko2.hage = true # => NoMethodError: undefined method 'hage=' moko2.hage # => NoMethodError: undefined method 'hage'
- Mokoクラスの特異クラス内でアクセッサを定義するとMokoクラスのみにおいて、アトリビュートが使えるようになる という事
- これは望まれた実装だろう
-
class Moko class << self attr_accessor :hage end end Moko.hage = true Moko.hage # => true String.hage = 'Hage-!' # => NoMethodError: undefined method 'hage=' String.hage # => NoMethodError: undefined method 'hage'
- モジュールを取り込んで特異メソッドを定義
- まずはインスタンスでやってみる
- つまり、インスタンスの特異メソッドの定義
- インスタンスに特異メソッドを定義するには、特異クラスのインスタンスメソッドを定義すればよい
- インスタンスの特異クラスをオープンしてインクルードしてみる
-
module M def hage p 'M::hage' end end class Moko end moko = Moko.new class << moko include M end moko.hage # => "M::hage"
- 次はクラスでやってみる
- つまり、クラスメソッドの定義
- クラスメソッドを定義するには、クラスの特異クラスのインスタンスメソッドを定義すればよい
- クラスの特異クラスをオープンしてインクルードしてみる
-
module M def hage p 'M::hage' end end class Moko class << self include M end end Moko.hage # => "M::hage"
- つまり、モジュールから特異メソッドを定義するには毎回特異クラスをオープンしなければならないという事?
- Object.extend があります
- インスタンスだろうがクラスオブジェクトだろうが、レシーバの特異クラスを開いてインクルードしてくれるんです
-
module M def hage p 'M::hage' end end class Moko end Moko.extend M Moko.hage # => "M::hage" moko = Moko.new moko.extend M moko.hage # => "M::hage"
- まずはインスタンスでやってみる
- アラウンドエイリアス
- メソッドに別名を付け、元のメソッドを再定義してみる。これは興味深いトリックの基本になる(らしい)
- 再定義したメソッドから別名を付けておいた元のメソッドを呼び出すことをアラウンドエイリアスと呼ぶ
-
class String alias_method :original_length, :length def length original_length > 3 ? 'big' : 'small' end end '1234'.length # => "big" '123'.length # => "small" '123'.original_length # => 3
- アラウンドエイリアスの欠点
- 新しいメソッド名でクラスを汚染してしまうこと
- これは些細な問題で、新しいメソッドをprivateにしてしまえばよい
- ※もちろん元のメソッドも一緒にprivateになってしまうような事はない
- これは些細な問題で、新しいメソッドをprivateにしてしまえばよい
- アラウンドエイリアスを行っているコードが2度呼ばれると 恐ろしいことになる。
- 元のメソッドへの参照は消え、新しいメソッドから新しいメソッドを呼ぶのか?それは無限ループじゃないのか?
- 一種のモンキーパッチであり、既存のコードを破壊しかねない問題は常に付きまとう
- 新しいメソッド名でクラスを汚染してしまうこと
- 他に似たような事を実現できる方法はないのか?
- Refinements は その影響下にてsuperを呼び出すとリファイン前のメソッドを呼ぶ事ができる
- もちろん Refinementsが有効なのは現在のスコープの終わり(あってる?)までなので、アラウンドエイリアスに比べ安全と言える
-
module StringRefinements refine String do def length super > 3 ? 'big' : 'small' end end end using StringRefinements '123'.length # => "small" '1234'.length # => "big"
- Module#prepend は先に呼び出されるしsuperもできるし
- Refinementsよりもローカルなものではないが、明示的でキレイな方法だとされている
-
module M def length super > 3 ? 'big' : 'small' end end class String prepend M end '123'.length # => "small" '124'.length # => "big"
- アラウンドエイリアスはベンダーのソース等、手が入れられない箇所にあるメソッドにロギング機能を追加したり
-
module ComparableWrapper def <=>(*argv) p 'Use <=>' super end end class Integer prepend ComparableWrapper end 1 == 1 # => "Use <=>" # => "Use <=>" # => "Use <=>" # => "Use <=>" # => "Use <=>" # => "Use <=>" # => true
- え・・?こんなに比較してんの? Verにもよるんだろうけど
- Integer#:+ を破壊して遊んだり
-
class Integer alias_method :original_plus, :+ def +(num) self.original_plus(num).original_plus(1) end end
- Refinements は その影響下にてsuperを呼び出すとリファイン前のメソッドを呼ぶ事ができる
コードにコードを記述させる(コードを記述させる)
- Kernel#eval
- 文字列を受け取り、そのコンテキストで実行する 特殊なくせに素直なやつ
- ナウいevalはヒアドキュメントを使ってたぶんこんな感じ
-
[:a, :b].each do |test| eval <<-end_eval def #{test} p '#{test}' end end_eval end a # => "a" b # => "b"
- evalさんの限界能力はBindingによって行われる、かもしれない
- 文字列を受け取り、そのコンテキストで実行する 特殊なくせに素直なやつ
- Kernel#binding
- Binding はスコープをオブジェクトにまとめたもの
- 例えばローカルスコープを取得し、持ち回す事ができる
-
class Moko def get_binding @test = 123 binding end end moko_binding = Moko.new.get_binding eval('p @test', moko_binding) # => 123
- TOPLEVEL_BINDING
- トップレベルのbindingは定数に定義されている
- やろうと思えばトップレベルにはいつでもどこからでもアクセスできるともいうこと
- pryでデバッグする時に binding.pry って書くやろ?あれはこれだったんじゃよ
- Kernel#eval 再び
- evalはeval族の中でも特殊な子であり、class_evalやinstance_evalのようにブロックを受け取るわけではない
- というか、class_evalやinstance_evalは文字列を受けることもできるので、逆に言えばevalが文字列しか受け取れない、と言うことでもある
-
as = [1, 2, 3] as.instance_eval('p self[1]') # => 2
-
- ブロックとコード文字列、どっちを使うべきか
- 可能であればブロックを使うべき
- コード文字列は強力で、責任と危険が伴う
- エディタのサポート機能の恩恵はもちろん受けられない
- その評価の瞬間までシンタックスエラーは報告されない
- そのコード文字列がどこから来たのか?外部から?
- それってとってもコードインジェクションだなって
-
str = 'length' p eval("[1, 2, 3].#{str}") # => 3 str = 'length; `ls`' p eval("[1, 2, 3].#{str}") # => うああああああ
- evalは追放すべき存在か?
- うまく define_method, object#send を使って回避することはできる
-
str = 'length' p eval("[1, 2, 3].send(:#{str})") # => 3 str = 'length; `ls`' p eval("[1, 2, 3].send(:#{str})") # => SyntaxError: (eval)
- evalは最適な使い方を見つけるのが簡単ではない
- Rubyはいくらか安全な機能を提供してくれている
- オブジェクトの汚染
- 潜在的に安全ではないオブジェクトに汚染汚染マークを付けてくれている
- ウェブフォーム、ファイル、コマンドライン、システム変数、等
- 汚染された文字列を使って生成された文字列も汚染される
-
'a'.tainted? # => false temp = gets.strip a temp.tainted? => true ('_' + temp + '_').tainted? # => true
- 自力で安全を確かめ、線マークを取り除きたければ Object#untaint を呼び出せばおk
- 全ての文字列が汚染されているか自力で確認しなければならないなら、安全ではない文字列を自力で追跡するのとあまり大差がない
- Rubyはセーフレベルというものを汚染オブジェクトと一緒に提供してくれている
- $SAFE = 1 のような感じで変更できる
- 一度変更したら、そのコンテキスト内のセーフレベルを下げる事はできない(下記のコードで一時的に変えたりしてるけど)
- セーフレベルにより、汚染されたオブジェクトを評価することもできなくしたりできた
- まぁ、Ruby2.0まではセーフレベルは0-4があったが、2.1からは0, 1しかない
- 様々な理由により、おもったよりも安全ではないことが分かってきたかららしい
-
# トップレベルのコンテキストにて、一時的にセーフレベルを上げてevalしている target = "p 'strings'.tainted?" binding = TOPLEVEL_BINDING p $SAFE # => 0 proc { $SAFE = 1 p $SAFE # => 1 eval(target, binding) # => false }.call p $SAFE # => 0
- $SAFE = 1 のような感じで変更できる
- Rubyはいくらか安全な機能を提供してくれている
- フックメソッド
- 色々な場面で発生するイベントをキャッチして実行させるよ
- 継承された(親クラスになった) => self.inherited(sub_class)
- Class.inherited は元々存在し、何もしないメソッドである
- 逆に、親クラス以降でもフックしてる可能性があるならsuperする必要があるんじゃないのか?
-
class String class << self def inherited(sub_class) p "#{self} ha #{sub_class} ni keisyou sareta" super # どうなの? end end end class Hage < String end # => "String ha Hage ni keisyou sareta"
- 他にも色々ある
- includeされた => Module#included(klass)
- prependされた => Module#prepended(klass)
- extendされた => Module#extended(klass)
- メソッドが追加された => Module#method_added(method)
- メソッドがremoveされた => Module#method_removeed(method)
- メソッドがundefされた => Module#method_undefined(method)
- 特異メソッドが => singleton_method_added, singleton_method_removed, singleton_method_undefined
- この辺りでの書籍のコード
- includeされたらクラスメソッド(attr_check)をextendするメソッド(self.included)を定義
- attr_checkedは、新たなメソッド(attribute=(), attribute)を定義するメソッドである
- attribute=()は、呼び出された時に、定義時のブロックを実行し、その真偽で値を保存するか、エラーを返すメソッドである
- ○○をするメソッドを定義するメソッドを定義するメソッドを定義 面白くなってきたじゃありませんか!
-
# 書籍のコードをちょっとだけ変えただけですんません module CheckAttributes def self.included(base_class) base_class.extend(ClassMethods) end module ClassMethods def attr_check(attribute, &validation) define_method "#{attribute}=" do |value| raise 'Hage-!' unless validation.call(value) instance_variable_set("@#{attribute}", value) end define_method attribute do instance_variable_get("@#{attribute}") end end end end class Moko include CheckAttributes attr_check(:hage) do |value| value < 100 end end moko = Moko.new moko.hage = 99 p moko.hage # => 99 moko.hage = 100 # => Hage-! (RuntimeError)
面白くなってきた所で、この本のこの部(第一部)は終了する。終了してしまうのだ
- メタプログラミングという山の頂上はこの足元なのだろうか?それともあの雲がかった辺りが、本当の頂上なのではないか?いや、頂上はとうに過ぎたのではなかったか?
- そんな気がしてくるのは この部のエピローグである最終章(1ページしかない)を読んだからだろうか?
- 「メタプログラミングというものなど存在しない」
- 「すべては」
- 「ただの」