ようへいの日々精進XP

よかろうもん

俺は Ruby について 1bit も理解していなかったので写経する 〜 組み込みクラス編(2) 〜

これは

  • Ruby Gold を受験するにあたって学んでいること等を写経したり, メモったりしています
  • Ruby 技術者認定試験合格教本」を主に参考にしており, 今回は第五章「組み込みクラス」を写経していきます
  • 実行例では主に pry を利用していますが, 例外が発生した際の実行過程出力(from から始まる行)は省略しています...すいません
[12] pry(main)> Baz2.new.private_method1
NoMethodError: private method `private_method1' called for #<Baz2:0x005606f46e2778>
from (pry):29:in `__pry__'

BasicObject クラス

Object クラス

オブジェクト ID

[1] pry(main)> a = "foo"
=> "foo"
[2] pry(main)> a.object_id
=> 47423007708960
[3] pry(main)> a.__id__
=> 47423007708960
  • オブジェクト ID は, 同じオブジェクトであれば同じ整数となる
  • 同じリテラルであっても, 基本的にはオブジェクト ID は異なる
  • TrueClass, FalseClass, NilClass, Symbol, Fixnum クラスのインスタンスは, 同じリテラルであれば同じオブジェクト ID となる

オブジェクトのクラス

[4] pry(main)> "foo".class
=> String
[5] pry(main)> :foo.class
=> Symbol
  • オブジェクトのクラスは class メソッドを利用する.

オブジェクトの比較

[1] pry(main)> a = "foo"
=> "foo"
[2] pry(main)> a.hash
=> -1583099881958492558
[3] pry(main)> a.object_id
=> 47334052318760
[4] pry(main)> b = "foo"
=> "foo"
[5] pry(main)> b.hash
=> -1583099881958492558
[6] pry(main)> b.object_id
=> 47334057818220
[7] pry(main)> a.eql?(b)
=> true
[8] pry(main)> a.equal?(b)
=> false
  • eqaul? メソッドではオブジェクト ID(object_id) を比較する
  • eql? メソッドはハッシュ(Hash) キーが同じかどうかを比較する
  • == はオブジェクトの内容が同じかどうかを比較する
  • === は case 式で利用されるメソッドで, クラスによって結果は依存する

オブジェクトのメソッド一覧

オブジェクトに定義されているメソッドを取得するには, 以下のメソッドを利用する.

メソッド名 用途
methods 全てのメソッドが呼び出し可能
private_methods プライベートメソッドのみ
protected_methods プロテクテッドメソッドのみ
public_methods パブリックメソッドのみ
singleton_methods 特異メソッドのみ

以下, 実行例.

[1] pry(main)> class OrenoClass
[1] pry(main)*   def ore1; end  
[1] pry(main)*   private  
[1] pry(main)*   def ore2; end  
[1] pry(main)*   protected  
[1] pry(main)*   def ore3;end  
[1] pry(main)* end  
=> :ore3
[2] pry(main)> 
[3] pry(main)> ore = OrenoClass.new
=> #<OrenoClass:0x005648754d1c78>
[4] pry(main)> ore.methods.grep(/^ore/)
=> [:ore1, :ore3]
[5] pry(main)> ore.private_methods.grep(/^ore/)
=> [:ore2]
[6] pry(main)> ore.protected_methods.grep(/^ore/)
=> [:ore3]
[7] pry(main)> ore.public_methods.grep(/^ore/)
=> [:ore1]
# 特異メソッドの定義
[8] pry(main)> def ore.ore4; end
=> :ore4
[9] pry(main)> ore.singleton_methods.grep(/^ore/)
=> [:ore4]

オブジェクトの凍結

オブジェクトの凍結には以下のメソッドを利用する.

  • freeze
[1] pry(main)> s = "hello"
=> "hello"
[2] pry(main)> s.freeze
=> "hello"
[3] pry(main)> s.upcase! rescue p $!
#<RuntimeError: can't modify frozen String>
=> #<RuntimeError: can't modify frozen String>
  • 標準クラスのオブジェクトでは, 凍結したあとで破壊的なメソッドを呼び出すと例外が発生する
  • 凍結状態を確認する場合, frozen? メソッドを使う
  • 凍結状態を元に戻すメソッドは用意されていない

凍結状態のオブジェクトでは, 以下のことが出来なくなる.

  • インスタンス変数の変更
  • 特異メソッドの定義, 特異クラスを使ったメソッド定義
[6] pry(main)> class Cat
[6] pry(main)*   attr_accessor :name  
[6] pry(main)*   def initialize(name)  
[6] pry(main)*     @name = name    
[6] pry(main)*   end    
[6] pry(main)* end  
=> :initialize
[7] pry(main)> c = Cat.new("Tama")
=> #<Cat:0x0055b96c489150 @name="Tama">
# インスタンスオブジェクトを freeze
[8] pry(main)> c.freeze
=> #<Cat:0x0055b96c489150 @name="Tama">
# 改めてインスタンスを生成
[9] pry(main)> c = Cat.new("Tama")
=> #<Cat:0x0055b96c53dba0 @name="Tama">
# 改めてインスタンスを生成したので, 問題なくインスタンス変数の変更が出来ている
[10] pry(main)> (c.name = "Piko") rescue p $!
=> "Piko"
# インスタンスオブジェクトを freeze
[11] pry(main)> c.freeze
=> #<Cat:0x0055b96c53dba0 @name="Piko">
# freeze した後なのでインスタンス変数の変更が出来なくなっている
[12] pry(main)> (c.name = "Piko") rescue p $!
#<RuntimeError: can't modify frozen Cat>
=> #<RuntimeError: can't modify frozen Cat>

但し, インスタンス変数の参照先のオブジェクトは変更可能.

[1] pry(main)> class Cat
[1] pry(main)*   attr_accessor :name  
[1] pry(main)*   def initialize(name)  
[1] pry(main)*     @name = name    
[1] pry(main)*   end    
[1] pry(main)* end  
=> :initialize
[2] pry(main)> c = Cat.new("Tama")
=> #<Cat:0x005578e6c852a8 @name="Tama">
[3] pry(main)> c.freeze
=> #<Cat:0x005578e6c852a8 @name="Tama">
[4] pry(main)> puts c.name.replace("Piko")
Piko
=> nil

オブジェクトの複製

オブジェクトの複製には以下のメソッドを利用する.

以下, 実行例.

[13] pry(main)> a = "hoge"
=> "hoge"
[14] pry(main)> a.object_id
=> 47434604593020
[15] pry(main)> b = a.clone
=> "hoge"
[16] pry(main)> b.object_id
=> 47434604585220
[17] pry(main)> c = a.dup
=> "hoge"
[18] pry(main)> c.object_id
=> 47434603558980

dup と clone は以下のような特徴がある.

メソッド名 詳細
dup 汚染状態, インスタンス変数, ファイナライザを複製
clone dup が複製する内容に加えて, 凍結状態, 特異メソッドも複製する

ここでの複製はシャローコピーであり, 自分自身の複製しか出来ない. 配列の要素の参照先は, clone メソッドや dup メソッドでは複製出来ない. また, コピーできないオブジェクト(true, false, nil, シンボル, 数値)に対して clone や dup を呼び出すと、例外 TypeError が発生する.

[1] pry(main)> class Cat
[1] pry(main)*   attr_accessor :name  
[1] pry(main)*   def initialize(name)  
[1] pry(main)*     @name = name    
[1] pry(main)*   end    
[1] pry(main)* end  
=> :initialize
[3] pry(main)> original = Cat.new("Tama")
=> #<Cat:0x0055996c057610 @name="Tama">
[4] pry(main)> copied = original.clone
=> #<Cat:0x0055996c08df80 @name="Tama">
[5] pry(main)> puts copied.equal?(original)
false
=> nil
[6] pry(main)> puts copied.name
Tama
=> nil

copied の @name に対して破壊的なメソッド(レシーバ自身を変更するメソッド)を呼び出すと, original の @name が指している文字列も変更される.

[7] pry(main)> copied.name.replace("Mike")
=> "Mike"
[8] pry(main)> puts original.name
Mike
=> nil

未定義メソッドの呼び出し

  • オブジェクトに定義されていないメソッドが呼び出された際, Ruby は method_missing メソッドを呼び出す
  • method_missing メソッドが定義されていない場合は NoMethodError になる
  • レシーバが指定されていない場合は NameError になる場合がある
[1] pry(main)> class Bar
[1] pry(main)*   def method_missing(name, *args)  
[1] pry(main)*     puts name    
[1] pry(main)*   end     
[1] pry(main)* end  
=> :method_missing
[2] pry(main)> b = Bar.new
=> #<Bar:0x0056283201a878>
# method_missing が呼ばれる
[3] pry(main)> b.hoge
hoge
=> nil
# レシーバが指定されていないので NameError となる
[4] pry(main)> hoge
NameError: undefined local variable or method `hoge' for main:Object

オブジェクトの文字列表現

  • オブジェクトの文字列表現を求めるには, to_s メソッドや inspect メソッドを使う
  • ほとんどの場合, サブクラスで定義し直されている
  • to_s メソッドは内容や値の文字列表現を返す
  • inspect メソッドはオブジェクトを人間が読める形式に変換する
[5] pry(main)> a = 1.2
=> 1.2
[6] pry(main)> a.to_s
=> "1.2"
[7] pry(main)> a.inspect
=> "1.2"
[8] pry(main)> Object.new.inspect
=> "#<Object:0x005628322068f8>"

以上

写経でした.