パーフェクトRuby 7章 「動的なプログラミング」 のまとめ
オープンなクラス
Rubyでは定義済みのクラスやメソッドを再定義することができる。
このような機能のことをRubyではオープンクラスと呼ぶ。
継承をしなくてもそのクラスを拡張できる。
使い方
#!/usr/bin/ruby
# すでに定義されているクラスの拡張
class Numeric
def steps
return [] if self <= 0
0.upto(self).to_a
end
def call_self
self
end
end
p 10.steps # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
p 0.steps # => []
p 7.call_self # => 7
オープンクラスの弊害
規定のクラスを拡張できてしまうので、例えば既存のメソッドを上書いた場合、正しく動いていたメソッドが動かなくなるなどの影響が出てしまう。そういうときは、 Refinementsを使う。Refinementsを使うと影響範囲をモジュール内などの限定的な範囲の中でメソッドを定義できる。
7-2 BasicObject#method_missing
BasicObject#method_missing
Rubyではメソッドが存在しない場合NoMethodErrorが出るがそのときに呼び出すことができるメソッドが method_missing。
メソッドが見つからない場合は、上位クラスまで探索し、BasicObjectのmethod_missingが呼ばれる。という流れ。
method_missingの使い方
引数 | 意味 |
---|---|
第一引数 | 呼び出しに失敗したメソッド名のシンボル |
第二引数 | 残りの引数 |
#!/usr/bin/ruby
# method_missingの挙動を確かめる
class Hoge
def initialize
end
def method_missing(name, *args)
puts name
puts *args
end
end
p Hoge.ancestors
# => [Hoge, Object, Kernel, BasicObject]
p BasicObject.private_method_defined?(:method_missing)
# => true
# method_missingはBasicobjectがもっている
hoge = Hoge.new
# 存在しないメソッドを呼んでみる
hoge.nai_method "args"
# => nai_method
# => args
method_missingの注意点
以下の2点に気をつける
- method_missingの再帰
- method_missingのオーバーライド
1. method_missingの再帰
method_missing内で、存在しないメソッドを呼ぶと
method_missingが再帰的に永遠に呼ばれてしまう。
そのようなことが起こると以下のようなエラーが出る。
SystemError: stack level too deep
2. method_missingのオーバーライド
また、method_missingを使っているクラスを継承したクラス内で、さらにmethod_missingを使う時もどこでメソッドが呼び出されるのかが分かりづらいので気をつける。
method_missingをオーバーライドする時は、必要なメソッドのみハンドリングを行う。
#!/usr/bin/ruby
# オーバーライド時のmethod_missingの挙動を確かめる
# method_missingを定義してある親クラスHoge
class Hoge
def initialize
end
def method_missing(name, *args)
puts name
puts *args
end
end
class Fuga < Hoge
def method_missing(name, *args)
# 親クラスがmethod_missingを利用している場合は、
# 必要なメソッドのハンドリングだけ行いあとはsuperに任せる
# というのが行儀が良いらしい
if name == :target
puts "target"
end
# 親クラスのmethod_missingよ呼ぶ
super
end
end
fuga = Fuga.new
fuga.target # Fugaのmethod_missingが呼ばれる
fuga.aaa 123 # 親クラスのHogeのmethod_missingが呼ばれる
eval
evalを使うと文字列をそのプログラミング言語の式として評価することができます。
eval族
Rubyに用意されているevalのためのメソッドたちは、eval族と呼ばれています。
メソッド名 | 動作 |
---|---|
Kernel.#eval | selfが呼び出された箇所として式を評価する |
Module#class_eval | レシーバのクラスをselfとして式を評価する |
Module#module_eval | レシーバのモジュールをselfとして式を評価する |
BasicObject#instance_eval | レシーバのオブジェクトをselfとして式を評価する |
evalを使うメソッドはいろいろあるがselfが異なる
#!/usr/bin/ruby
# レシーバの違いを確かめる
eval "p self" # => main
o = Object.new
o.instance_eval { p self } # => => #<Object:0x007fe94a86ab38>
o.class.class_eval { p self } # => Object
Kernel.module_eval {p self } # => Kernel
以下、それぞれの動作について
Kernel.#eval
Kernelモジュールで定義されているevalメソッドは、引数に渡された文字列をプログラムとして実行します。
#!/usr/bin/ruby
# Kernel.#eval
p eval "1 + 1" # => 2
a = 10
eval "a *= 2"
# aの値も変更される
p a # => 20
evalを利用してメソッドを定義することもできる。
#!/usr/bin/ruby
# evalを使ったメソッドの定義
# 以下は、インスタンス変数が代入された時に、
# 別のインスタンス変数に値を保持するメソッド
class LoggingInstanceVal
hoge = %w(first_val sec_val third_val)
hoge.each do |v|
eval <<-END_OF_DEF
attr_reader :#{v}, :before_#{v}
def #{v}=(val)
@before_#{v} = @#{v}
@#{v} = val
end
END_OF_DEF
end
end
obj = LoggingInstanceVal.new
obj.first_val = 3
obj.first_val = 4
p obj.first_val # => 4
p obj.before_first_val # => 3
obj.third_val = :third_val
obj.third_val = "third_val"
p obj.third_val # => third_val
p obj.before_third_val # => :third_val
evalとBindingオブジェクト
Bindingオブジェクトとは
あるコンテキストで定義された変数やメソッドをまとめたオブジェクト
※ コンテキスト:プログラムを実行するにあたっての条件などの判断材料
Kernel.#evalの式の評価が変わる
Kernel.#evalの第二引数にbindingオブジェクトを渡すと式がbindingオブジェクトのスコープで評価される
eval "@instance_val" # => nil
eval "local_val" # => NameError: undefined local variable or method `local_val'
# そのコンテキストで定義された変数やメソッドしか参照できない
#!/usr/bin/ruby
# About binding object in eval method.
class Hoge
attr_accessor :ins_val
def initialize
@ins_val = "This is instance val"
end
def hogehoge
local_val = "This is local val"
binding
end
private
def private_method
p "private_method"
end
end
h = Hoge.new
binding_obj = h.hogehoge
# インスタンス変数
eval "p @ins_val", binding_obj # => "This is instance val"
# プライベートメソッド
eval "private_method", binding_obj # => "private_method"
# 以下のような呼び方もできる
binding_obj.eval "private_method"
module_val/ class_val/ instance_val
Kernel.#evalと違い、式を評価するコンテキストとなるオブジェクトやクラス、モジュールを指定してevalする
Kernel.#evalとの違い
- Bindingオブジェクトを使用しなくても式を評価できる
module_eval/class_eval/instance_val
モジュール、クラスで式を評価するためのメソッド。
#!/usr/bin/ruby
class EvalHoge
@hoge = 10
class << self
def cls_method
p @hoge
end
end
end
EvalHoge.cls_method # => 10
# class_evalを使って値変更
EvalHoge.class_eval "@hoge = 5"
EvalHoge.cls_method # => 5
モジュールもオブジェクトも同じく
使いどころ
クラス定義式やメソッド定義式では以下のようなことができない
val = 3
class Hoge
@hoge = val # クラスの外側は参照できない
end
でも、class_eval式を利用すると
val = 3
class Hoge
end
Hoge.class_eval do
@hoge = val
end
で定義ができる。
instance_evalの場合は
class Hoge
attr_reader :hoge
@hoge = 3
end
h = Hoge.new
h.instance_eval do
@hoge = 10
end
p h.hoge # => 10
プライベートメソッドも呼び出すことができる
#!/usr/bin/ruby
class Hoge
attr_reader :hoge
private
def private_method
@hoge = 100
end
end
h = Hoge.new
# instance_evalを使うとプライベートメソッドも呼べる
h.instance_eval do
private_method
end
p h.hoge # => "100"
module_eval/class_eval/instance_evalの違い
- module_eval/class_eval => インスタンスメソッドが定義される
- instance_eval => 特異メソッドが定義される
参考
パーフェクトRuby