bar_1

contents_map

2017年11月8日水曜日

Rubyの実験: Object/Class/Module/Kernelにメソッドを追加するとどうなるか?

Object/Class/Module/Kernelにメソッドを追加すると, これらや一般のクラス, モジュールやインスタンスに対してどのような影響があるか, 調べるコードを書いてみた. 追加するメソッドはインスタンスメソッドと特異メソッドで, 両方publicの可視属性です.


コード

#

class Object
  def meth_test_oi; "#{self} - #{__method__}"; end
  def self.meth_test_ois; "#{self} - #{__method__}"; end
end
class Class
  def meth_test_ci; "#{self} - #{__method__}"; end
  def self.meth_test_cis; "#{self} - #{__method__}"; end
end
class Module
  def meth_test_mi; "#{self} - #{__method__}"; end
  def self.meth_test_mis; "#{self} - #{__method__}"; end
end
module Kernel
  def meth_test_ki; "#{self} - #{__method__}"; end
  def self.meth_test_kis; "#{self} - #{__method__}"; end
end

class FooClass; end
module BarModule; end

puts "## Object/Class/Module/Kernelにメソッドを追加したとき,"
puts "## 適当なクラス/モジュール/インスタンスにおいて, メソッドが追加されているか否か"
target_methods = [
  :meth_test_oi,
  :meth_test_ci,
  :meth_test_mi,
  :meth_test_ki,
  :meth_test_ois,
  :meth_test_cis,
  :meth_test_mis,
  :meth_test_kis,
]

target_classes   = [ Object, Class, Module, ]
target_modules   = [ Kernel, ]
target_instances = [
  [1,2,3],
  Array,
  FooClass,
  BarModule,
]

targets = target_classes +
          target_modules +
          target_instances

target_methods.each do |mm|
  puts "* #{mm} の結果"
  targets.each do |t|
    begin
      p t.send mm
    rescue =>e
      msg = e.message.gsub(/\n+Did you mean.+$/m, '')
      puts "#{msg} (#{e.class})"
    end
  end

  puts
end

#

結果

## Object/Class/Module/Kernelにメソッドを追加したとき,
## 適当なクラス/モジュール/インスタンスにおいて, メソッドが追加されているか否か
* meth_test_oi の結果
"Object - meth_test_oi"
"Class - meth_test_oi"
"Module - meth_test_oi"
"Kernel - meth_test_oi"
"[1, 2, 3] - meth_test_oi"
"Array - meth_test_oi"
"FooClass - meth_test_oi"
"BarModule - meth_test_oi"

* meth_test_ci の結果
"Object - meth_test_ci"
"Class - meth_test_ci"
"Module - meth_test_ci"
undefined method `meth_test_ci' for Kernel:Module (NoMethodError)
undefined method `meth_test_ci' for [1, 2, 3]:Array (NoMethodError)
"Array - meth_test_ci"
"FooClass - meth_test_ci"
undefined method `meth_test_ci' for BarModule:Module (NoMethodError)

* meth_test_mi の結果
"Object - meth_test_mi"
"Class - meth_test_mi"
"Module - meth_test_mi"
"Kernel - meth_test_mi"
undefined method `meth_test_mi' for [1, 2, 3]:Array (NoMethodError)
"Array - meth_test_mi"
"FooClass - meth_test_mi"
"BarModule - meth_test_mi"

* meth_test_ki の結果
"Object - meth_test_ki"
"Class - meth_test_ki"
"Module - meth_test_ki"
"Kernel - meth_test_ki"
"[1, 2, 3] - meth_test_ki"
"Array - meth_test_ki"
"FooClass - meth_test_ki"
"BarModule - meth_test_ki"

* meth_test_ois の結果
"Object - meth_test_ois"
"Class - meth_test_ois"
"Module - meth_test_ois"
undefined method `meth_test_ois' for Kernel:Module (NoMethodError)
undefined method `meth_test_ois' for [1, 2, 3]:Array (NoMethodError)
"Array - meth_test_ois"
"FooClass - meth_test_ois"
undefined method `meth_test_ois' for BarModule:Module (NoMethodError)

* meth_test_cis の結果
undefined method `meth_test_cis' for Object:Class (NoMethodError)
"Class - meth_test_cis"
undefined method `meth_test_cis' for Module:Class (NoMethodError)
undefined method `meth_test_cis' for Kernel:Module (NoMethodError)
undefined method `meth_test_cis' for [1, 2, 3]:Array (NoMethodError)
undefined method `meth_test_cis' for Array:Class (NoMethodError)
undefined method `meth_test_cis' for FooClass:Class (NoMethodError)
undefined method `meth_test_cis' for BarModule:Module (NoMethodError)

* meth_test_mis の結果
undefined method `meth_test_mis' for Object:Class (NoMethodError)
"Class - meth_test_mis"
"Module - meth_test_mis"
undefined method `meth_test_mis' for Kernel:Module (NoMethodError)
undefined method `meth_test_mis' for [1, 2, 3]:Array (NoMethodError)
undefined method `meth_test_mis' for Array:Class (NoMethodError)
undefined method `meth_test_mis' for FooClass:Class (NoMethodError)
undefined method `meth_test_mis' for BarModule:Module (NoMethodError)

* meth_test_kis の結果
undefined method `meth_test_kis' for Object:Class (NoMethodError)
undefined method `meth_test_kis' for Class:Class (NoMethodError)
undefined method `meth_test_kis' for Module:Class (NoMethodError)
"Kernel - meth_test_kis"
undefined method `meth_test_kis' for [1, 2, 3]:Array (NoMethodError)
undefined method `meth_test_kis' for Array:Class (NoMethodError)
undefined method `meth_test_kis' for FooClass:Class (NoMethodError)
undefined method `meth_test_kis' for BarModule:Module (NoMethodError)
インスタンス関係や継承関係を理解するのにいいかも.

解説

特異メソッド(クラスメソッド)の結果については割とわかりやすいと思われるため, インスタンスメソッドを追加した影響について, Classクラスの結果を例に見ておこう:
* meth_test_ci の結果
"Object - meth_test_ci"
"Class - meth_test_ci"
"Module - meth_test_ci"
undefined method `meth_test_ci' for Kernel:Module (NoMethodError)
undefined method `meth_test_ci' for [1, 2, 3]:Array (NoMethodError)
"Array - meth_test_ci"
"FooClass - meth_test_ci"
undefined method `meth_test_ci' for BarModule:Module (NoMethodError)
Classクラスに追加したインスタンスメソッド Class#meth_test_ci は, Object, Class, Moduleのクラスメソッドにもなっている. またArray, FooClassからもクラスメソッドとして呼び出せる. これはなぜだろうか?
Object, Classなど上記5つは, クラスである(newできる). と同時に, Classクラスのインスタンスでもある: たとえばObject.class =>Class, Object.instance_of? Class =>true である (ややこしいがModuleについてもModule.class =>Classだ).
したがって, Class#meth_test_ciは, Classのインスタンス経由で呼び出せる. これは
class BarClass
  def meth; 1; end
end
bar_inst = Bar.new
bar_inst.meth # =>1
と同じことだ.
では, Class#meth_test_ci (インスタンスメソッド) を, なぜ Class.meth_test_ci (クラスメソッド) として呼び出せるのか?
実はClass.class =>Class, Class.instance_of? Class =>trueである. つまり, Classクラスをインスタンスと見た場合, そのクラスはClass—-すなわち自分自身—-である.
bar_inst.methを呼び出すのと同様の例を作ってみよう.
class Class
  def meth; 2; end
end
inst = Class
class_inst = Class.new

inst.meth       # =>2
class_inst.meth # =>2

inst.class       # =>Class
class_inst.class # =>Class
おわかりいただけたであろうか……?
Paolo Perrotta (角 征典 訳) の『メタプログラミングRuby』のオブジェクトモデルの章にあるオブジェクトモデルの図を読むと, 理解しやすいと思う. 初版は2010年に技術評論社から, 2015年には第2版がオライリーから出ている.

0 件のコメント:

コメントを投稿

何かありましたら、どうぞ: