パーフェクトRubyお勉強フェスティバル 第7章「動的なプログラミング」
久々におふとん王国を全力で統治しています!楽しい!
パーフェクトRubyの勉強記録,更新していきます.
「静的」と「動的」
Rubyは動的なプログラミング言語ですが,ここで静的と動的について深く考えたことがなかったので,調べてみます.
テスト駆動開発が動的なプログラミング言語の弱点をカバーすることができる,というあたりの話は面白いですね. 「テスト駆動開発」も常々読んでみたいと思っています.(給料が出たら…!)
- 作者: Kent Beck,和田卓人
- 出版社/メーカー: オーム社
- 発売日: 2017/10/14
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
オープンクラス
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)
その他いろいろと細かい話があるのですが割愛...
はて,自分が使うことはあるのだろうかと思い調べる.
クラス拡張のメリットを享受しつつも,拡張した影響範囲をコントロールしたいというニーズを満たすということですかねえ. 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"