- この記事は
- そして, この記事は
- オブジェクトモデル
- オープンクラス
- オブジェクトモデルの内部
- クイズ:引かれていない線
- メソッドを呼び出す時に何が起きているのか?
- クイズ:絡み合ったモジュール
- まとめ
- 以上
この記事は
メタプログラミング Ruby 第 2 版を写経したり, 感想を書いたりしています.
そして, この記事は
既に 2018 年だけど...
初老丸 Advent Calendar 2017 23 日目の記事です.
オブジェクトモデル
- このメソッドはどのクラスに所属するものなのか?
- このモジュールをインクルードしたら何が起きるのか?
オープンクラス
Bookworm の to_alphanumeric メソッド
以下のメソッドは, Bookworm に実装されているテープラベルに書名を印字する為の機能. この機能は, 全ての句読点や特殊文字を削除して, アルファベットとスペースだけを残すというもの.
def to_alphanumeric(s) s.gsub(/[^\w\s]/, '') end
このメソッドには, 以下のようなユニットテストがついている.
require 'test/unit' class ToAlphanumericTest < Test::Unit::TestCase det test_strips_non_alphanumeric_characters assert_equal '3 the Magic Number', to_alphanumeric('#3, the *Magic, Number*?') end end
ビルはつかさず, String クラスをオープンして, to_alphanumeric メソッドを追加する.
class String def to_alphanumeric s.gsub(/[^\w\s]/, '') end end
ユニットテストも String#to_alphanumeric
を使うように変更.
require 'test/unit' class ToAlphanumericTest < Test::Unit::TestCase def test_strips_non_alphanumeric_characters assert_equal '3 the Magic Number', '#3, the *Magic, Number*?'.to_alphanumeric end end
クラス定義
- Ruby では, クラスを定義するコードとその他のコードに違いは無い
- クラス定義の中に, 好きなコードを配置出来る
3.times do class C puts "Hello." end end
以下, 実行例.
[2] pry(main)> 3.times do [2] pry(main)* class C [2] pry(main)* puts "Hello" [2] pry(main)* end [2] pry(main)* end Hello Hello Hello => 3
同じ名前のクラスが 3 つ定義されている訳ではなくて, クラスを再オープンして定義している. 以下のコードで確認することが出来る.
class D def x; 'x'; end end class D def y; 'y'; end end obj = D.new obj.x obj.y
以下, 実行例.
[7] pry(main)> class D [7] pry(main)* def x; 'x'; end [7] pry(main)* end => :x [8] pry(main)> class D [8] pry(main)* def y; 'y'; end [8] pry(main)* end => :y [9] pry(main)> obj = D.new => #<D:0x007fd87330fef0> [10] pry(main)> obj.x => "x" [11] pry(main)> obj.y => "y"
class D
が 2 回定義されているわけではなく, 最初に class D
と x
メソッドが定義されて, 2 回目の class D
では既に存在している class D
を再びオープンして y
メソッドを定義している.
Ruby の class
は, クラス宣言というよりもスコープ演算子のようなもの.
Monetize の例
Monetize という gem での実装例.
require 'monetize' bargain_price = Monetize.from_numeric(99, "USD") bargain_price.format #=> "$99.00"
以下のように, 数値を Money オブジェクトに変換するショートカットとして Numeric#to_money
が使える.
require 'monetize' standard_price = 100.to_money("USD") standard_price.format #=> "$100.00"
これは, 以下のように標準クラスの Numeric クラスを再オープンしてメソッドを定義している.
class Numeric def to_money(currency = nil) Monetize.from_numeric(self, currency || Money.default_currency) end end
オープンクラスの問題点
オープンクラスはカッコイイ部分とダークサイドも存在する...ということで, Array クラスに replace
メソッドを追加した場面.
[12] pry(main)> [].methods.grep /^re/ => [:reverse_each, :reverse, :reverse!, :reject, :reject!, :replace, :repeated_permutation, :repeated_combination, :reduce, :remove_instance_variable, :respond_to?]
上記の通り, うっかり replace
メソッドを追加してしまうと, 既存のメソッドを上書いてしまう可能性がある.
オブジェクトモデルの内部
オブジェクトの中身
インスタンス変数
class MyClass def my_method @v = 1 end end obj = MyClass.new obj.class
以下, 実行例.
[1] pry(main)> class MyClass [1] pry(main)* def my_method [1] pry(main)* @v = 1 [1] pry(main)* end [1] pry(main)* end => :my_method [2] pry(main)> obj = MyClass.new => #<MyClass:0x007fdb2b9ae490> [3] pry(main)> obj.class => MyClass
Object#instance_variables
を呼び出すことで, インスタンス変数を確認することが出来る.
[4] pry(main)> obj.instance_variables => [] [5] pry(main)> obj.my_method => 1 [6] pry(main)> obj.instance_variables => [:@v]
インスタンス変数は値が代入された時に初めて出現する為, 同じクラスのオブジェクトであってもインスタンス変数の数が異なることがある.
インスタンスメソッド
Object#methods
を呼び出せば, メソッドの一覧を取得することが出来る.
[7] pry(main)> obj.methods.grep(/my/) => [:my_method]
殆どのオブジェクトは Object クラスから多くのメソッドを継承しているので, メソッド一覧はとても長いものとなるが, Array#grep
を利用すれば意図したメソッドを取得することが出来る.
メソッドはオブジェクトではなく, クラスに存在する.
メソッドに関する重要な違いを認識する
まだまだ混乱している俺がメソッドに関する重要な違いを認識する場面...
- 「MyClass は my_method を持っている」→ MyClass.mymethod のようなクラスメソッドのように呼び出されると思われ混乱する
- my_method を MyClass のインスタンスメソッドと呼ぶべき
まとめると...
- オブジェクトのインスタンス変数はオブジェクトに存在する
- オブジェクトのメソッドはオブジェクトのクラスに存在する
クラスの真相
クラスはオブジェクト
- クラスはオブジェクト
- クラスにもクラスがある→ Class クラス
[8] pry(main)> "hello".class => String [9] pry(main)> String.class => Class
[10] pry(main)> Class.instance_methods(false) => [:allocate, :new, :superclass]
new
メソッドについては, オブジェクトを生成する時にいつも使っているallocate
メソッドはnew
メソッドを補助するものsuperclass
メソッドは継承の概念と関連しており頻繁に利用する
[11] pry(main)> Array.superclass => Object [12] pry(main)> Object.superclass => BasicObject [13] pry(main)> BasicObject.superclass => nil
モジュール
Class クラスのスーパークラスを確認する場面.
[14] pry(main)> Class.superclass => Module
- Class のスーパークラスは Module である
- 全てのクラスはモジュールである
- クラスはオブジェクトの生成やクラスを継承する為の 3 つのインスタンスメソッド(
new
,allocate
,superclass
)を追加したモジュール
定数
スコープ
- 大文字で始まる参照は, クラス名やモジュール名を含めて, 全て定数
- 定数と変数の重要な違いはスコープ
定数のパス
# ファイルのようにパスを使って定数を参照することが出来る # 定数のパスは, コロン 2 つで区切る module M class C X = 'ある定数' end C::X #=> "ある定数" end M::C::X #=> "ある定数" # ルートを示すコロン 2 つで書き始めれば, 外部の定数を絶対パスで指定可能 Y = 'ルートレベルの定数' module M Y = 'M にある定数' Y #=> "M にある定数" ::Y #=> "ルートレベルの定数" end
- インスタンスメソッド
Module#constants
は現在のスコープにある全ての定数を返す - クラスメソッド
Module.constants
は, 現在のプラグラムのトップレベル定数を返す - パスが必要であれば
Module.nesting
を利用する
M.constants #=> [:C, :Y] Module.constants.include? :Object #=> true Module.constants.include? :Module #=> true # Module.nesting Module M class C module M2 Module.nesting #=> [M::C::M2, M::C, M] end end end
オブジェクトとクラス
オブジェクトとは何だろうか.
クラスとは何だろうか.
ネームスペース
- Ruby のクラス名は慣習としてパスカルケース(最初の大文字の単語を連結したもの
ThisTextIsPascalCased
)を使う - クラスとモジュールの名前の衝突回避の為にネームスペースにラップする
module Bookworm class Text ...
クイズ:引かれていない線
サンプルコード
class MyClass; end obj1 = MyClass.new obj2 = MyClass.new
解答
- Object のクラスは何?
# Object のクラスは Class [4] pry(main)> Object.class => Class
- Module のスーパークラスは何?
# Module のスーパークラスは Object [5] pry(main)> Module.superclass => Object
- Class のクラスは何?
# Class のクラスは Class [6] pry(main)> Class.class => Class
- 以下のコードを実行した場合, obj3 はどこに入る?
# 全てのオブジェクトが自分だけのインスタンス変数の一覧を持っている # 他のオブジェクトとは関係が無い, 同じクラスのオブジェクトであっても, お互いに無関係である [4] pry(main)> obj3 = MyClass.new => #<MyClass:0x007f82c406ae98> [5] pry(main)> obj3.instance_variable_set("@x", 10) => 10
メソッドを呼び出す時に何が起きているのか?
メソッド探索
レシーバと継承チェーン
Ruby はメソッドを呼び出すと以下の 2 つのことを行う.
- メソッドを探す(メソッド探索)
- メソッドを実行する(
self
が必要になる)
レシーバとは...
- 呼び出すメソッドが属するオブジェクトのこと
- mystring.reverse と書けば,
my_string
がレシーバである
継承チェーンとは...
メソッド探索を一言で纏めると...
Ruby がレシーバのクラスに入り, メソッドを見つけるまでの継承チェーンを上がること
class MyClass def my_method; 'my_method()'; end end class MySubClass < MyClass end obj = MySubClass.new obj.my_method()
- 右へ一歩, それから上へ(one step to the right, then up)
- レシーバのクラスに向かって右へ一歩進み, メソッドが見つかるまで継承チェーンを上に進む
- クラスの継承チェーンは ancestors メソッドで確認することが出来る
[5] pry(main)> MySubClass.ancestors => [MySubClass, MyClass, Object, PP::ObjectMixin, Kernel, BasicObject]
モジュールとメソッド探索
- 継承チェーンにはモジュールも含まれる
- モジュールをクラスに(あるいは別のモジュール)にインクルードすると Ruby はモジュールを継承チェーンに挿入する
[6] pry(main)> module M1 [6] pry(main)* def my_method [6] pry(main)* 'M1#my_method()' [6] pry(main)* end [6] pry(main)* end => :my_method [7] pry(main)> class C [7] pry(main)* include M1 [7] pry(main)* end => C [8] pry(main)> class D < C; end => nil [9] pry(main)> D.ancestors => [D, C, M1, Object, PP::ObjectMixin, Kernel, BasicObject]
prepend メソッド
- include と同じように動作するが...
- インクルードしたクラス(インクルーダー)の上ではなく, 下にモジュールが挿入される
[10] pry(main)> class C2 [10] pry(main)* prepend M1 [10] pry(main)* end => C2 [11] pry(main)> class D2 < C2; end => nil [12] pry(main)> D2.ancestors => [D2, M1, C2, Object, PP::ObjectMixin, Kernel, BasicObject]
多重インクルード
[1] pry(main)> module M1; end => nil [2] pry(main)> module M2 [2] pry(main)* include M1 [2] pry(main)* end => M2 [3] pry(main)> module M3 [3] pry(main)* prepend M1 [3] pry(main)* include M2 [3] pry(main)* end => M3 [4] pry(main)> M3.ancestors => [M1, M3, M2]
- M3 が M1 をプリペンドして, それから M2 をインクルードしている
- M2 も M1 をインクルードしているが, M1 は既に M3 の継承チェーンに含まれている為, この include には何の効果も無い
- モジュールが既にチェーンに存在していたら, Ruby は 2 回目の挿入を無視する
Kernel
[5] pry(main)> Kernel.private_instance_methods.grep(/^pr/) => [:printf, :print, :proc]
- Object クラスが Kernel モジュールをインクルードしているので, 全てのオブジェクトの継承チェーンに Kernel モジュールが挿入されている
- Ruby コードは常にオブジェクトの内部で実行されるので, Kernel モジュールのメソッドはどこからでも呼び出せる
以下, Kernel メソッドを試す場面.
[6] pry(main)> module Kernel [6] pry(main)* def hagepika [6] pry(main)* 'hagepika' [6] pry(main)* end [6] pry(main)* end => :hagepika [7] pry(main)> hagepika => "hagepika"
メソッドの実行
レシーバの参照
- メソッドを呼び出す時, Ruby は 2 つのことを行う(メソッドを探す, メソッドを実行する)
def my_method temp = @x + 1 my_other_method(temp) end
- @x も my_other_method もレシーバに属している
- レシーバとは, my_method が最初に呼び出されたオブジェクトのこと
self キーワード
- Ruby のコードはオブジェクト(カレントオブジェクト)の内部で実行される
- カレントオブジェクトは self とも呼ばれる
[1] pry(main)> class MyClass [1] pry(main)* def testing_self [1] pry(main)* @var = 10 [1] pry(main)* my_method [1] pry(main)* self [1] pry(main)* end [1] pry(main)* [1] pry(main)* def my_method [1] pry(main)* @var = @var + 1 [1] pry(main)* end [1] pry(main)* end => :my_method [2] pry(main)> obj = MyClass.new => #<MyClass:0x007fa6bc006198> [3] pry(main)> obj.testing_self => #<MyClass:0x007fa6bc006198 @var=11>
トップレベル
- オブジェクトのメソッドを呼び出すと, そのオブジェクトが self になる
- メソッドを呼び出さない時には, 誰が self になるのか?
[1] pry(main)> self => main [2] pry(main)> self.class => Object
上記のように Ruby のプログラムを開始すると, main と呼ばれるトップレベルコンテキストが self になる.
クラス定義と self
- クラスやモジュールの定義の内側(メソッドの外側)では, self の役割はクラスやモジュールそのものになる
[3] pry(main)> class MyClass [3] pry(main)* self [3] pry(main)* end => MyClass
- メソッドを呼び出すと, Ruby は「一歩右へ, それから上へ」のルールに従って, メソッドを探し始める
- メソッドが見つかると, レシーバを self にしてメソッドを呼び出す
private
private メソッドには, 一つのシンプルなルールが適用される.
明示的なレシーバを付けて, private メソッドを呼び出すことが出来ない
以下, 実行例.
[4] pry(main)> class C [4] pry(main)* def public_method [4] pry(main)* self.private_method [4] pry(main)* end [4] pry(main)* private [4] pry(main)* def private_method; end [4] pry(main)* end => :private_method [5] pry(main)> C.new.public_method NoMethodError: private method `private_method' called for #<C:0x007f8d92b1ef28> from (pry):8:in `public_method'
上記の例は, private メソッドが 2 つのルール(private ルール)に従っていることを示している.
- 自分以外のオブジェクトのメソッドを呼び出すには, レシーバを明示的に指定する必要がある
- private のついたメソッドを呼び出す時はレシーバを指定出来ない
Refinements
苦い記憶
- オープンクラスを使って String クラスにメソッドを追加した例
class String def to_alphanumeric gsub(/[^\w\s]/, '') end end
このクラスの問題点は, 変更がグローバルに及ぶことだった.
Refinements という魔術
以下のように, モジュール定義の中で refine を呼び出す.(String クラスに新しい to_alphanumeric メソッドを refine している.)
module StringExtensions refine String do def to_alphanumeric gsub(/[^\w\s]/, '') end end end
これを呼び出す場合には, using メソッドを使って明示的に有効にする必要がある.
using StringExtensions
以下, 実行例.(using を呼び出した時点から, 呼び出したソースファイルにある全てのコードが変更される.)
[8] pry(main)> module StringExtensions [8] pry(main)* refine String do [8] pry(main)* def to_alphanumeric [8] pry(main)* gsub(/[^\w\s]/, '') [8] pry(main)* end [8] pry(main)* end [8] pry(main)* end => #<refinement:String@StringExtensions> # using する前 [9] pry(main)> "foo *bar* baz!".to_alphanumeric NoMethodError: undefined method `to_alphanumeric' for "foo *bar* baz!":String from (pry):22:in `__pry__' # using した後 [14] pry(main)> using StringExtensions; "foo *bar* baz!".to_alphanumeric => "foo bar baz"
ちなみに, irb や pry で Refinements したメソッドを呼び出す場合には, 上記のように一行で実行する必要があった.
Ruby 2.1 からは...
- モジュール定義の中でも using を呼び出せるようになった
- Refinements はモジュール定義の終わりまで有効になる
[1] pry(main)> module StringExtensions [1] pry(main)* refine String do [1] pry(main)* def reverse [1] pry(main)* "esrever" [1] pry(main)* end [1] pry(main)* end [1] pry(main)* end => #<refinement:String@StringExtensions> [2] pry(main)> module StringStuff [2] pry(main)* using StringExtensions [2] pry(main)* "my_string".reverse [2] pry(main)* end => "esrever" [3] pry(main)> "my_string".reverse => "gnirts_ym"
上記は, String#reverse メソッドにパッチを当てているが, それが有効なのは StringStuff モジュール定義の内部だけである.
Refinements の有効範囲
- refine ブロックそのもの
- using を呼び出した場所からモジュールの終わりまで(モジュール定義にいる場合), 又はファイルの終わりまで(トップレベルにいる場合)
Refinements が有効になっている場合
- 新しいメソッドを定義することが出来る
- 既存のメソッドを再定義することが出来る
- モジュールの include や prepend も可能
- Refinements が有効になっているコードは, リファインされたコードよりも優先される
通常のオープンクラスが出来ることなら何でも出来る.
クイズ:絡み合ったモジュール
絡み合ったモジュール
module Printable def print # ... end def prepare_cover # ... end end module Document def print_to_screen prepare_cover format_for_screen print end def format_for_screen # ... end def print # ... end end class Book include Document include Printable # ... end
print_to_screen メソッドが, 正しい print メソッドを呼び出していない
Printable モジュールと Document モジュールのどちらの print メソッドを呼び出しているのだろうか...
以下, ancestors 例.
[4] pry(main)> module Printable [4] pry(main)* def print [4] pry(main)* # [4] pry(main)* end [4] pry(main)* def prepare_cover [4] pry(main)* # [4] pry(main)* end [4] pry(main)* end => :prepare_cover [5] pry(main)> module Document [5] pry(main)* def print_to_screen [5] pry(main)* prepare_cover [5] pry(main)* format_for_screen [5] pry(main)* print [5] pry(main)* end [5] pry(main)* def format_for_screen [5] pry(main)* # [5] pry(main)* end [5] pry(main)* def print [5] pry(main)* # [5] pry(main)* end [5] pry(main)* end => :print [6] pry(main)> class Book [6] pry(main)* include Document [6] pry(main)* include Printable [6] pry(main)* end => Book [7] pry(main)> Book.ancestors => [Book, Printable, Document, Object, PP::ObjectMixin, Kernel, BasicObject]
Document#print を呼び出したい場合...
... class Book include Printable include Document end ...
上記のように Book クラスでモジュールのインクルード順番を入れ替えれば良い.
[10] pry(main)> class Book2 [10] pry(main)* include Printable [10] pry(main)* include Document [10] pry(main)* end => Book2 [11] pry(main)> Book2.ancestors => [Book2, Document, Printable, Object, PP::ObjectMixin, Kernel, BasicObject]
まとめ
- オブジェクトは複数のインスタンス変数とクラスへのリンクで構成される
- オブジェクトのメソッドはオブジェクトのクラスに住んでいる(インスタンスメソッド)
- クラスは Class クラスのオブジェクト
- Class は Module のサブクラス
- 定数はファイルシステムのようなツリー状に配置されている
- クラスは BasicObject まで続く継承チェーンを持っている
- メソッドを呼び出すと, 一歩右へ進み, それから継承チェーンを上へ向かって進む
- クラスにモジュールを include すると, そのクラスの継承チェーンの真上にモジュールが挿入される
- モジュールを prepend すると, そのクラスの継承チェーンの真下にモジュールが挿入される
- メソッドを呼び出す時には, レシーバが self になる
- モジュール(又はクラス)を定義する時には, そのモジュールが self になる
- Refinements は using を呼び出したところから, ファイルやモジュールの定義が終わる所までの限られた部分のみで有効になる