LoginSignup
3
3

More than 5 years have passed since last update.

破壊的操作が出来る数値クラスもどきを考えた

Last updated at Posted at 2012-11-23

言う間でもなくRubyの Fixnum はイミュータブルで、破壊的変更を許すメソッドを持ち合わせていない。

result1 = %w(foo bar buz qiiz).each_with_object(0) do |str, num|
  num += str.length
end

上記のようなコードは「0」を返す。Rubyのブロックには「オブジェクトの参照のコピー」が渡されるため、0自体を直接操作できない以上そうなる(何でこうなるかが分からない人はRubyを沢山勉強しよう!)。

でも、例えばeachでぶん回して操作成功した件数知りたい、とか言う場合、ぶっちゃけ破壊的操作できる数値オブジェクトが欲しくない?

書いてみた。

numeric_wrap.rb
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#classObject#is_a?Object#inspectとかも全部上書きされているため、見た目上まるっきりFixnumとして取り扱うことができてしまう。でも、 Fixnum と直接の継承関係はない

num = NumericWrap.new(0)
num.class #=> Fixnum

直感的に凄い不気味……。

Delegate-all patternは他には、ActiveSupport::Durationに使われている。というかこの実装を見てパクった。

3
3
1

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
3
3