言う間でもなくRubyの Fixnum
はイミュータブルで、破壊的変更を許すメソッドを持ち合わせていない。
result1 = %w(foo bar buz qiiz).each_with_object(0) do |str, num|
num += str.length
end
上記のようなコードは「0
」を返す。Rubyのブロックには「オブジェクトの参照のコピー」が渡されるため、0
自体を直接操作できない以上そうなる(何でこうなるかが分からない人はRubyを沢山勉強しよう!)。
でも、例えばeachでぶん回して操作成功した件数知りたい、とか言う場合、ぶっちゃけ破壊的操作できる数値オブジェクトが欲しくない?
書いてみた。
class NumericWrap < BasicObject
def initialize(n)
@num = n
end
def self.regard_as_destructive(*ops)
ops.each do |op|
define_method(op) do |other|
@num = @num.send(op, other)
self
end
end
end
# Picked up randomly...
regard_as_destructive :+, :-, :*, :/, :%, :**, :&, :|
def method_missing(*a)
@num.send(*a)
end
end
result1 = %w(foo bar buz qiiz).each_with_object(0) do |str, num|
num += str.length
end
result2 = %w(foo bar buz qiiz).each_with_object(NumericWrap.new(0)) do |str, num|
num += str.length
end
p result1
#=> 0
p result2
#=> 13
BasicObject
を継承したクラスをラッパーにして、必要なメソッド以外は全部 method_missing
で委譲するようなパターンを勝手に「Delegate-all pattern」と名付けたけど、他に良い名前がありそう(メタプログラミングRubyとかでBlank Slateという名前で同じようなものを説明してたような気がする)。
Object#class
、Object#is_a?
、Object#inspect
とかも全部上書きされているため、見た目上まるっきりFixnum
として取り扱うことができてしまう。でも、 Fixnum
と直接の継承関係はない 。
num = NumericWrap.new(0)
num.class #=> Fixnum
直感的に凄い不気味……。
Delegate-all patternは他には、ActiveSupport::Duration
に使われている。というかこの実装を見てパクった。