ようへいの日々精進XP

よかろうもん

俺は Ruby について 1bit も理解していなかったので写経する 〜 memo 編〜

例外 ~ ensure 節がある時の評価値 ~

こちらこちらの記事を読ませて頂いて気になったので pry してみた.

[1] pry(main)> def f
[1] pry(main)*   begin
[1] pry(main)*     1
[1] pry(main)*   rescue  
[1] pry(main)*     2
[1] pry(main)*   else  
[1] pry(main)*     3
[1] pry(main)*   ensure  
[1] pry(main)*     4
[1] pry(main)*   end  
[1] pry(main)* end  
=> :f
[2] pry(main)> f
=> 3

うっかり ensure 以下の処理が評価されて 4 が表示されると思いきや, ドキュメントに以下のように書かれていて驚いた.

begin式全体の評価値は、本体/rescue節/else節のうち 最後に評価された文の値です。また各節において文が存在しなかったときの値 はnilです。いずれにしてもensure節の値は無視されます。

結果として else 以降の 3 が表示される.

メソッド内の定数更新

同じく こちらこちらの記事を読ませて頂いて気になったので pry してみた.

[1] pry(main)> X = 1
=> 1
[2] pry(main)> def f
[2] pry(main)*   X
[2] pry(main)* end  
=> :f
[3] pry(main)> f
=> 1
[4] pry(main)> def f
[4] pry(main)*   X = 2
[4] pry(main)* end  
SyntaxError: (eval):3: dynamic constant assignment
  X = 2
     ^
[4] pry(main)> def f
[4] pry(main)*   X += 1
[4] pry(main)* end  
SyntaxError: (eval):3: dynamic constant assignment
  X += 1
      ^
[4] pry(main)> def f
[4] pry(main)*   X << 1
[4] pry(main)* end  
=> :f
[5] pry(main)> f
=> 2

メソッド内での定数更新はシンタックスエラーとなるが, << メソッドは破壊的ではないのでエラーにならない...そうなのか...

Object ENV

引き続き こちらこちらの記事を読ませて頂いて気になったので pry してみた.

[10] pry(main)> ENV.class
=> Object
[6] pry(main)> ENV['PATH']
=> "/path/to/..."
[8] pry(main)> ENV['PATH'].tainted?
=> true
[9] pry(main)> ENV['PATH'].frozen?
=> true
  • Hash と似たインターフェースを持つが, Hash と異なり、ENV のキーと値には文字列しかとることができない

p と print

  • print は Kernel モジュールのメソッド(Kernel#print)
    • 引数を順に標準出力 $stdout に出力する
    • 引数が与えられない時には変数 $_ の値を出力する
    • 文字列以外のオブジェクトが引数として与えられた場合には, to_s メソッドにより文字列に変換してから出力する
  • p も同様に Kernel モジュールのメソッド(Kernel#p)
    • 引数を人間に読みやすい形に整形して改行と順番に標準出力 $stdout に出力する
    • 引数の inspect メソッド の返り値と改行を順番に出力する
  • inspect はオブジェクトクラスのメソッド(Object#inspec)

オブジェクトの複製

# シャローコピー(配列やハッシュ自体は複製されるがそれぞれの要素までは複製されない) の例
[7] pry(main)> a = ["foo", "bar", "baz"]
=> ["foo", "bar", "baz"]
[8] pry(main)> b = a.dup
=> ["foo", "bar", "baz"]
[9] pry(main)> a[0].upcase!
=> "FOO"
[10] pry(main)> p a
["FOO", "bar", "baz"]
=> ["FOO", "bar", "baz"]
[11] pry(main)> p b
["FOO", "bar", "baz"]
=> ["FOO", "bar", "baz"]
# シャローコピーの例(2)
[1] pry(main)> a = ["foo", "bar", "baz"]
=> ["foo", "bar", "baz"]
[2] pry(main)> b = a.dup
=> ["foo", "bar", "baz"]
# dup したオブジェクトを破壊的メソッドで更新
[3] pry(main)> b[0].upcase!
=> "FOO"
# コピー元まで更新されている
[4] pry(main)> p a
["FOO", "bar", "baz"]
=> ["FOO", "bar", "baz"]
[5] pry(main)> p b
["FOO", "bar", "baz"]
=> ["FOO", "bar", "baz"]
# clone でも同様のことが言える
[1] pry(main)> a = ["foo", "bar", "baz"]
=> ["foo", "bar", "baz"]
[2] pry(main)> b = a.clone
=> ["foo", "bar", "baz"]
[3] pry(main)> b[0].upcase!
=> "FOO"
[4] pry(main)> p a
["FOO", "bar", "baz"]
=> ["FOO", "bar", "baz"]
[5] pry(main)> p b
["FOO", "bar", "baz"]
=> ["FOO", "bar", "baz"]

上記は, 元の配列の要素を破壊的に変更すると, 複製した配列にも影響が及ぶ例.

  • clone や dup はシャローコピーとなる
  • シャローコピーとなる為, コピー元のオブジェクトを更新すると, コピー先のオブジェクトも更新される
  • 同様にコピー先のオブジェクトを更新するとコピー元のオブジェクトも更新される

シャローコピーに対して, 配列やハッシュの要素も含めてコピーする(ディープコピー)場合には, marshal ライブラリを以下のように利用する.

[1] pry(main)> a = ["foo", "bar", "baz"]
=> ["foo", "bar", "baz"]
[2] pry(main)> b = Marshal.load(Marshal.dump(a))
=> ["foo", "bar", "baz"]
[4] pry(main)> b[0].upcase!
=> "FOO"
[5] pry(main)> p a
["foo", "bar", "baz"]
=> ["foo", "bar", "baz"]
[6] pry(main)> p b
["FOO", "bar", "baz"]
=> ["FOO", "bar", "baz"]
  • Marshal#dump と Marshal#load を利用する
  • コピーした配列 b を更新してもコピー元の配列 a は更新されていない

protected と private メソッド

[1] pry(main)> class C1
[1] pry(main)*   def method1  
[1] pry(main)*     private_method    
[1] pry(main)*     protected_method    
[1] pry(main)*   end    
[1] pry(main)*   def method2  
[1] pry(main)*     self.private_method    
[1] pry(main)*     self.protected_method    
[1] pry(main)*   end    
[1] pry(main)*   private  
[1] pry(main)*   def private_method  
[1] pry(main)*     p 'private_method'    
[1] pry(main)*   end    
[1] pry(main)*   protected  
[1] pry(main)*   def protected_method  
[1] pry(main)*     p 'protected_method'    
[1] pry(main)*   end      
[1] pry(main)* end  
=> :protected_method
[2] pry(main)> c = C1.new
=> #<C1:0x0055dfe7fdbbb0>
# private メソッドはインスタンスから直接呼べない
[3] pry(main)> c.private_method
NoMethodError: private method `private_method' called for #<C1:0x0055dfe7fdbbb0>
# 同様に protected メソッドはインスタンスから直接呼べない
[4] pry(main)> c.protected_method
NoMethodError: protected method `protected_method' called for #<C1:0x0055dfe7fdbbb0>
# レシーバー無しだと呼べる
[5] pry(main)> c.method1
"private_method"
"protected_method"
=> "protected_method"
# レシーバー有りでは private メソッドは呼べない
# protected メソッドは呼べる
[6] pry(main)> c.method2
NoMethodError: private method `private_method' called for #<C1:0x0055dfe7fdbbb0>
  • protected で指定したメソッドはレシーバーから参照可能
  • private で指定したメソッドはレシーバーから参照不可
  • protected と private はクラス外から直接参照不可

undef と undef_method と remove_method

[1] pry(main)> class C1
[1] pry(main)*   def method1  
[1] pry(main)*     p 'method1'    
[1] pry(main)*   end    
[1] pry(main)*   def method2  
[1] pry(main)*     p 'method2'    
[1] pry(main)*   end    
[1] pry(main)*   def methodx  
[1] pry(main)*     p 'methodx1'    
[1] pry(main)*   end    
[1] pry(main)* end  
=> :methodx
[2] pry(main)> 
[3] pry(main)> class C2 < C1
[3] pry(main)*   alias method3 method1  
[3] pry(main)*   undef method1  
[3] pry(main)*   alias method4 method2  
[3] pry(main)*   undef method2  
[3] pry(main)*   def methodx  
[3] pry(main)*     p 'methodx2'    
[3] pry(main)*   end    
[3] pry(main)* end  
=> :methodx
[4] pry(main)> c = C2.new
=> #<C2:0x005563e0ff6ec0>
# undef method1 で定義が無効になっている為
[5] pry(main)> c.method1
NoMethodError: undefined method `method1' for #<C2:0x005563e0ff6ec0>
# undef method2 で定義が無効になっている為
[6] pry(main)> c.method2
NoMethodError: undefined method `method2' for #<C2:0x005563e0ff6ec0>
[7] pry(main)> c.method3
"method1"
=> "method1"
[8] pry(main)> c.method4
"method2"
=> "method2"
[9] pry(main)> c.methodx
"methodx2"
=> "methodx2"

undef も undef_method もメソッド定義を取り消す機能があるが, 以下のような違いがある.

  • undef
    • メソッド名には識別子かシンボルを指定する
  • undef_method
    • メソッド名には String か Symbol を指定する
    • undef_method の場合はスーパークラスに同名のメソッドがあってもその呼び出しはエラーになる

以下, remove_method の実行例.

[10] pry(main)> class C2 < C1
[10] pry(main)*   remove_method :methodx  
[10] pry(main)* end  
=> C2
[11] pry(main)> c = C2.new
=> #<C2:0x005563e10eb358>
# スーパークラスで定義されている methodx が呼ばれている
[12] pry(main)> c.methodx
"methodx1"
=> "methodx1"
  • remove_method しても, スーパークラスに同名のメソッドがある場合には, そのメソッドが呼ばれる.

以上

ホントに俺は Ruby について 1bit も理解していなかったんだなあと痛感.

ドキュメントをはじめ, 色々なサイトを参考にさせて頂きました. 有難うございます.