Object#to_bool

Why Every Ruby Developer Should Learn Smalltalk(なぜ全てのRuby開発者がSmalltalkを学ぶべきなのか)」っていう文章がtwitterで流れてきたので読んでみた。概ね同意。みんなSmalltalkを学ぶべき。

で、その中にこんな一文があった。

みんなよくRubyでは0や空配列が真と見なされることに文句を言う。

if []
  puts 'true'
else
  puts 'false'
end

上のスニペットを実行すると'true'と表示されるだろう。Rubyにはこの動作を変える手段はない。

そもそも空配列や0が偽と見なされる方が変じゃないのかとか、この動作が簡単に変えられたらメンテで死ぬとか色々と思うことはあるけど、例えばcase/whenでの比較は===メソッドを再定義することで変えられるので、if/unlessにも動作を変える手段があってもいいんじゃないか。

ということでやってみた。if/unlessは条件のto_boolを呼び出してから分岐する。

parse.yの一部。

| k_if expr_value then
  compstmt
  if_tail
  k_end
    {
    /*%%%*/
        $$ = NEW_IF(NEW_CALL(cond($2), rb_intern("to_bool"), 0), $4, $5);
        fixpos($$, $2);
    /*%
        $$ = dispatch3(if, $2, $4, escape_Qundef($5));
    %*/
    }

prelude.rb

class Object
  def to_bool
    self
  end
end

試してみる。

class Array
  def to_bool
    not empty?
  end
end

print '[] is '
if []
  puts :true
else
  puts :false
end

print '[1] is '
if [1]
  puts :true
else
  puts :false
end

class Integer
  def to_bool
    0 != self
  end
end

puts "0 is #{0 ? :true : :false}"
puts "1 is #{1 ? :true : :false}"

実行。

$ ./ruby -Ilib -I. sample.rb 
[] is false
[1] is true
0 is false
1 is true

実装はともかく、動作としてはこれはこれでありかも。

ちなみにこんなこともできる。

class TrueClass
  def to_bool
    false
  end
end

class FalseClass
  def to_bool
    true
  end
end

puts 'true?' if true
puts 'false?' if false

実行。

$ ./ruby -Ilib -I. sample.rb
false?

いい具合にカオス。