動的なプログラミング

パーフェクトRuby 7章　「動的なプログラミング」　のまとめ

----

### オープンなクラス
Rubyでは定義済みのクラスやメソッドを再定義することができる。
このような機能のことをRubyではオープンクラスと呼ぶ。
継承をしなくてもそのクラスを拡張できる。

#### 使い方
```Numericクラスにメソッドを追加する
#!/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の使い方
| 引数 | 意味 |
|:-----------|------------:|
| 第一引数    | 呼び出しに失敗したメソッド名のシンボル |
| 第二引数    | 残りの引数 |


```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をオーバーライドする時は、必要なメソッドのみハンドリングを行う。

```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オブジェクトのスコープで評価される

```bindingオブジェクトを使っていない時
eval "@instance_val"  # => nil
eval "local_val"      # => NameError: undefined local variable or method `local_val'
# そのコンテキストで定義された変数やメソッドしか参照できない
```

```bindingオブジェクトを使っている時
#!/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