パーフェクトRubyお勉強フェスティバル 第7章「動的なプログラミング」

久々におふとん王国を全力で統治しています!楽しい!

oftonkingdom.hatenablog.com

パーフェクトRubyの勉強記録,更新していきます.

「静的」と「動的」

Rubyは動的なプログラミング言語ですが,ここで静的と動的について深く考えたことがなかったので,調べてみます.

clown.hatenablog.jp

テスト駆動開発が動的なプログラミング言語の弱点をカバーすることができる,というあたりの話は面白いですね. 「テスト駆動開発」も常々読んでみたいと思っています.(給料が出たら…!)

テスト駆動開発

テスト駆動開発

オープンクラス

Rubyでは既存のクラスの拡張が可能.定義後にメソッドを追加することができる.

ただ注意しなければならないのは,メソッドを上書きできてしまうので,オープンクラスによる拡張を行っているライブラリを使う場合などは想定外の動作となる可能性がある.

Refinements

オープンクラスの影響範囲を限定するための機能がRefinementsである.

まずModule#refineの引数に拡張したいクラスを指定し,ブロック内でメソッドを定義する. そしてModule#usingで取り込む.

refinements.rb

#Numericクラスに対し,オブジェクトを布団の枚数として表示するメソッドを追加
module RefineOfton
  refine Numeric do
    def count_ofton
      puts "布団が#{self}"
    end
  end
end

#Classのコンテキストでusing
class Ofton
  using RefineOfton
  def ofton_number
    5.count_ofton
  end
end

Ofton.new.ofton_number

結果

布団が5枚

別ファイル「using_refineofton.rb」からトップレベルでModule#usingし,読み込み

using_refineofton.rb

require_relative 'refinements'

using RefineOfton

3.count_ofton

結果

 布団が3枚

さらに別ファイル「require_using_refineofton.rb」からModule#requireして,「count_ofton」を呼び出した場合は,NoMethodErrorが発生する.

require './using_refineofton'

5.count_ofton

結果

布団が3枚
require_using_refineofton.rb:3:in `<main>': undefined method `count_ofton' for 5:Integer (NoMethodError)

その他いろいろと細かい話があるのですが割愛...

はて,自分が使うことはあるのだろうかと思い調べる.

qiita.com

クラス拡張のメリットを享受しつつも,拡張した影響範囲をコントロールしたいというニーズを満たすということですかねえ. Ruby on Railsでも使われているみたいだし,Railsチュートリアルをやりながら見られたらいいかなと思いますね.

BasicObject#method_missing

BasicObject#method_missingは「NoMethodError」をraiseするメソッドである.

メソッドの探索順序通りにメソッドを探していくと,最上位の親クラス(BasicObjectクラス)まで行き着く.そこでもメソッドがないというときにこのBasicObject#method_missingが呼ばれ,「NoMethodError」となるわけですね.

method_missingをオーバーライドして,メソッドが見つからなかった場合の処理を自分で定義することで,NoMethodErrorの発生を回避できる.

サンプルコードで出てきた「Object#send」って?

この謎は10章で解けるのでまた詳しく.

注意点

  • method_missing内でさらに存在していないメソッドを呼び出すとループしてしまう
  • 継承時,継承ツリーに存在するクラスでmethod_missingがオーバーライドされている場合がある

eval(イーバル)

この章で個人的に一番面白いと思ったのがこれです.evaluate,式を評価するということです.

Kernel.#eval

引数に渡された「文字列」を式として評価し,実行する.

何が嬉しいかというと

  • 似たようなメソッド定義を簡潔に書ける
  • メソッドを作るメソッドが定義できる

やってみました.似たようなメソッド定義を簡潔に書いてみる.

class Ofton
  animals = %w(cat dog pig)
  
  animals.each do |animal|
    eval <<-END_OF_DEF
      def #{animal}
        puts "#{animal}"
      end
    END_OF_DEF
  end
end

obj = Ofton.new

obj.cat
obj.dog
obj.pig

結果

cat
dog
pig

メソッドを作るメソッドについては,動的にsetterやgetterを追加するメソッドの例が書籍に載っていました.

evalとBindingオブジェクト

Kernel.#bindingでBindingオブジェクトは得られる.

Bindingオブジェクトを使うことでそのコンテキストの内容を他のコンテキストに持ち込める.

これの何がよいかというとevalで式を評価するコンテキストを任意のコンテキストにできる.

class Ofton
  #Bindingオブジェクトを返すメソッド
  def show_king
    @king = 'ofton king'
    binding
  end
end

ofton_obj = Ofton.new
ofton_binding_obj = ofton_obj.show_king

p eval "@king" #ここからは「@king」は見ることができないが
p eval "@king", ofton_binding_obj #Bindingオブジェクトによってコンテキストを指定しているので@kingが見える

結果

nil
"ofton king"

Bindingオブジェクトを使わなくてもモジュール,クラス,インスタンスのコンテキストを指定してevalがしたい場合

module_eval,class_eval,instance_evalを使う. これらを使うと文字列だけでなく渡したブロックも式として評価し実行できる.

class Ofton
  #クラスインスタンス変数
  @class_instance_val
  class << self
    def class_instance_val
      @class_instance_val
    end
  end
  
  #インスタンス変数
  attr_reader :instance_val
end

#class_evalによるクラスインスタンス変数の再設定
Ofton.class_eval do
  @class_instance_val = 'new class instance val'
end

p Ofton.class_instance_val

#instance_evalによるインスタンス変数の再設定
obj = Ofton.new
obj.instance_eval do
  @instance_val = 'new instance val'
end

p obj.instance_val


#Oftonクラスの中からは呼び出せない変数(ここではval)も参照できる
val = 'ofton'

#class_evalによるクラスインスタンス変数の再設定
Ofton.class_eval do
  @class_instance_val = val
end

p Ofton.class_instance_val

#instance_evalによるインスタンス変数の再設定
obj.instance_eval do
  @instance_val = val
end

p obj.instance_val

結果

"new class instance val"
"new instance val"
"ofton"
"ofton"