Help us understand the problem. What is going on with this article?

動的なプログラミング

More than 3 years have passed since last update.

パーフェクト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点に気をつける
1. method_missingの再帰
2. 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メソッドは、引数に渡された文字列をプログラムとして実行します。

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした