かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Ruby Bool値かどうか型チェックしたい。ついでにis_bool?メソッドを作ってみた。

Rubyで変数がBoolean型かどうかチェックしたい。
かなりレアケースな気がするけれど、この前参加した勉強会で要望があったのでトライしてみた。

RubyのTrueとFalse

  • false ... false, nil の時
  • true ... false, nil 以外全てtrue

Rubyには is_bool? という関数はない

true.is_bool?
# => undefined method `is_bool?' for true:TrueClass (NoMethodError)

エラーになります。

is_a?(クラス名)というメソッドを使うとオブジェクトが引数のクラスのインスタンスかどうか判定できるので、これを利用する。
trueTrueClassfalseFalseClassインスタンス
BooleanBoolClassってクラスは無いっぽい。

val = true
p val.is_a?(FalseClass) || val.is_a?(TrueClass) # => true

val = false
p val.is_a?(FalseClass) || val.is_a?(TrueClass) # => true

val = nil
p val.is_a?(FalseClass) || val.is_a?(TrueClass) # => false

チェックできるけど記述が長いので何回もチェックが必要だとめんどくさそう...

is_a? を使わない方法を考えてみる

値を!!でtrue/falseに変換して比較したらいけそうな気がしました。

def is_bool(v)
  !!v === v
end

p is_bool true  # => true
p is_bool false # => true
p is_bool nil   # => false
p is_bool 1     # => false
p is_bool 0     # => false
p is_bool ""    # => false
p is_bool true.to_s  # => false
p is_bool false.to_s # => false

なんとなく上手くいっている気もしますが、Ruby===演算子は型チェックをしているのではなく、左辺のオブジェクトによって判定の挙動が異なるらしいので本当にこれでOKなのか少し自信がありません。
ついでに関数化してみたのですが、is_boolって関数名で引数取る感じが何となくイケてない気がします...

他の言語を使っていると、ついつい「===演算子は==演算子よりも更に厳密な同値判定演算子でしょ?」なんて思ってしまいますが、ところがどっこい、Rubyの場合はむしろ===演算子のほうが柔軟な場合もあり得るのです。
Object#===の説明を見てみるとわかりますが、基本的にこの===演算子は、サブクラスで再定義されていない限り、単にObject#==を呼び出すだけなんですね。

出展: Rubyのcase式と===演算子について - しばそんノート

TrueClass, FalseClassでは#===が定義されていないようだったので、Object#===と同じなのではないかと思います。
ってコトは !!v==vでも同じことっぽい!?

Object
obj === other → true or false
Case Equality – For class Object, effectively the same as calling #==, but typically overridden by descendants to provide meaningful semantics in case statements.

出展: https://ruby-doc.org/core-2.2.2/Object.html#method-i-3D-3D-3D

Rubyは定義済みのクラスにメソッドとかを追加できるっぽい

手持ちの初めてのRubyという本を見ていると、オブジェクトとクラスの章にRubyは「定義済みのクラスに対して、いつでも定義を追加することができます。」とありました。
Objectクラスにis_bool?というメソッドを追加することも可能っぽかったので試してみました。

class Object
  def is_bool?
    !!self === self
  end
end

p true.is_bool?  # => true
p false.is_bool? # => true
p nil.is_bool?   # => false
p 1.is_bool?     # => false
p 0.is_bool?     # => false
p "".is_bool?    # => false
p true.to_s.is_bool?  # => false
p false.to_s.is_bool? # => false

一応 Object.is_bool? でBool値かどうか判定できるっぽものが出来ました。
メソッド内の判定をself.is_a?(FalseClass) || self.is_a?(TrueClass)にした方が判定としては確実かもしれません。

ただ、個人的に大元になるObjectクラスにメソッドを追加するのは正直どーなの?という感覚があるので良し悪しが判断できません。
こうするとCoolだよとかRubyっぽいよ。ってのがあれば教えてください。


[参考]

初めてのRuby

初めてのRuby