2
2

『メタプログラミングRuby』138ページのサンプルコードをIRBで実行するとエラーになる

Posted at

はじめに

メタプログラミングRuby 第2版』という書籍を読んでいて、混乱した箇所があったため、備忘録としてまとめます。

この記事におけるバージョンは Ruby 3.3 です。

問題

p138ではModule#alias_methodの説明がされています。

Module#alias_methodは既存のメソッドにエイリアス(別名)をつけるメソッドです。

次のサンプルコードで説明されています。

class_definitions/wrapper_around_alias.rb
class String
  alias_method :real_length, :length

  def length
    real_length > 5 ? 'long' : 'short'
  end
end

"War and Peace".length       #=> "long"
"War and Peace".real_length  #=> 13

ところがこのサンプルコードをIRBで実行すると次のエラーが発生しました。

irb(main):001* class String
irb(main):002*   alias_method :real_length, :length
irb(main):003* 
irb(main):004*   def length
irb(main):005*     real_length > 5 ? 'long' : 'short'
irb(main):006*   end
irb(main):007> end
irb(main):008> 
irb(main):009> "War and Peace".length
irb(main):010> "War and Peace".real_length
An error occurred when inspecting the object: #<TypeError: String can't be coerced into Integer>
Result of Kernel#inspect: #<Integer:0x000000000000001b>
=> 
irb(main):011> 

おかしいなと思い、今度はサンプルファイルを次のように修正して、ターミナルからファイルを実行してみます。

class_definitions/wrapper_around_alias.rb
class String
  alias_method :real_length, :length

  def length
    real_length > 5 ? 'long' : 'short'
  end
end

# puts で出力する
puts "War and Peace".length
puts "War and Peace".real_length

するとこちらは狙い通りの挙動となりました。

$ ruby class_definitions/wrapper_around_alias.rb
long
13
$ 

つまり、IRBで実行するときだけエラーが発生してしまう状況です。

IRBでのみ呼び出されるメソッドに問題があるはずですが、特定に時間がかかりました。

理由

IRBでは、結果を表示する際にObject#inspectメソッドが自動的に呼び出されます。

このときにprettyprintが使用され、出力を整形します。

ですが、今回の例ではString#lengthメソッドが上書きされているため、本来Integer型を返すべき部分でString型が返されている状況です。

その結果、以下の箇所でエラーが発生していました。

ruby/lib/prettyprint.rb
  def text(obj, width=obj.length) # lengthを上書きしているため、width は String 型の値
    if @buffer.empty?
      @output << obj
      @output_width += width  # width は Integer 型を想定しているためエラーが発生
    else
      text = @buffer.last
      unless Text === text
        text = Text.new
        @buffer << text
      end
      text.add(obj, width)
      @buffer_width += width
      break_outmost_groups
    end
  end

上記の@output_widthはInteger型を想定していますが、widthにはString型が格納されています。

これによりTypeError: String can't be coerced into Integerが発生しました。

なお、この挙動はバグではなく、String#lengthを上書きした結果生じる正常な動作です。

おわりに

手を動かしながら書籍を読み進めていたため、このエラーに気づきました。

「組み込みクラスのメソッドは安易に上書きすべきではない」というのを身をもって体験できてよかったです。

この記事に誤りがありましたら、コメントにてご指摘いただけますと幸いです。

参考資料

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2