Ruby で 1 == true を行うと何が起きるのか

元ネタ

まあ 1true は別オブジェクトだからなーと思いつつ 1 == true は例外を返してほしいなーとか 1 == truetrue の方がいいんじゃね?と思ったりしたのでそもそも 1 == がどういう挙動なのかみてみました。

1 == obj を行うと obj.== が呼ばれる

1 == obj みたいな比較を行うと obj が数値でない場合は obj.== を呼び出し、それで比較を行います。

class X
  def ==(other)
    true
  end
end

x = X.new

# 内部で x == 1 が呼び出される
p 1 == x
# => true

またこの時に注意するのは 1 == xx == 1 が等価ではないことです。
具体的にいうと戻り値が異なります。

class X
  def ==(other)
    "X#=="
  end
end

x = X.new

p 1 == x   # => true
p x == 1   # => "X#=="

1 == true するとどうなるのか

では 1 == true を行うとどうなるのかというと内部で true == 1 が呼ばれます。
なので本来は TrueClass#== が呼ばれるはずなんですが TrueClass#== は定義されておらず、実際には親クラスの BasicObject#== が呼び出され結果的に object_id で比較を行います。
むずかしい。
また、次のように TrueClass#== を書き換えると結果を書き換える事ができます。

class TrueClass
  def ==(other)
    !!other
  end
end

p 1 == true     # => true

まあこれはこれで?

まとめ

  • 1 == obj すると obj.== が呼ばれる
  • なので 1 == obj がどうなるのかは obj.== の実装に依存する
  • TrueClass/FalseClass では #== が定義されておらず object_id で比較が行われる

余談

個人的に 1 == "hoge" みたいなのは例外になって欲しいなーと思っていたんですが、この場合は "hoge".== が呼ばれ String#== の実装に依存します。
で、 String#== の実装は比較できない場合は例外が発生するのではなくて false を返すようになっています。
流石にこれの挙動を変えるのは互換性の面から見てむずかしそうな気がする…。

ちなみに < などで比較すると例外が発生します。

# error: comparison of Integer with true failed (ArgumentError)
1 < true
1 <= false
1 > "hoge"