【ネタ】Rubyでインクリメント可能な整数オブジェクトを作ってみた

# 完成図

```ruby
x = MutableInteger.new(3)

x #=> 3


### 一見普通のIntegerっぽく振る舞う ###

x + 1   #=> 4
x - 2   #=> 1
x * 3   #=> 9
x.even? #=> false


### Integerっぽいのに破壊的に操作できる！ ###

x.next! #=> 4
x.next! #=> 5
x       #=> 5


### 前置インクリメントをサポート！！ ###

++x     #=> 6
++x     #=> 7
x       #=> 7
```

# 実装
## `#next!` メソッドの実装

ruby の数値オブジェクトは不変(immutable)なので、`Integer` クラスを再オープンしても、破壊的なメソッドを実装することはできない。

```ruby
class Integer
  def next!
    # ここに何を書いても、インクリメントを実装できない
  end
end
```

そのため、 **整数インスタンスをラップしたクラスを作り、すべてのメソッドを委譲(delegate)する** という方法をとる。

```ruby
class MutableInteger
  def initialize(integer)
    raise "引数は整数にしてね" unless integer.is_a?(Integer)
    @component = integer
  end

  def next!
    @component = @component.next
    self
  end

  def to_i
    @component
  end

  def method_missing(name, *args)
    super unless @component.methods.include?(name)

    @component.send(name, *args)
  end
end
```

なお、`#next!` メソッドで self を返しているのは、

```ruby
x.next!.next!.next!
```

のような記述をサポートするためである。

さて、さっそく irb で動かしてみよう

```ruby
x = MutableInteger.new(3)
#=> #<MutableInteger:0x00007fccef93b5c0 @component=3>

y = x + 2
#=> 5

x.even?
#=> false

x.next!
#=> #<MutableInteger:0x00007fccef93b5c0 @component=4>

x.even?
#=> true
```

概ね想定どおりの動きだが、出力が数値っぽくない。


## ディスプレイ表示を数値っぽくする

`#to_s` および `#inspect` の結果が `Integer` クラスと同じになるように修正する。

```diff
class MutableInteger
  def initialize(integer)
    raise "引数は整数にしてね" unless integer.is_a?(Integer)
    @component = integer
  end

  def next!
    @component = @component.next
    self
  end

  def to_i
    @component
  end

+ def to_s(base = 10)
+   @component.to_s(base)
+ end
+ alias inspect to_s

  def method_missing(name, *args)
    super unless @component.methods.include?(name)

    @component.send(name, *args)
  end
end
```

今回のコーディング中に知ったのだが、`Integer#to_s` メソッドは基数を引数に取れるらしい。

```ruby
5.to_s(2) #=> "101"
```

デフォルトだと10進数に変換したいので、 `base = 10` とデフォルト値を設定した。

これで表示が数値っぽくなった。

```ruby
x = MutableInteger.new(3)
#=> 3

x.next!
#=> 4

x.to_s
#=> "4"

x.to_s(2)
#=> "100"
```

## 演算結果を `MutableInteger` にする part1

ここまでの記述だけでは、四則演算などを挟むとクラスが `Integer` に戻ってしまう。

```ruby
x = MutableInteger.new(3)
y = x + 2

x.class #=> MutableInteger
y.class #=> Integer

y.next! #=> NoMethodError (undefined method `next!' for 5:Integer)
```

これを解決するため、メソッドの処理結果が `Integer` だった場合、`MutableInteger` に変換して返すように変更する。

```diff
  def method_missing(name, *args)
    super unless @component.methods.include?(name)

-   @component.send(name, *args)
+   ret = @component.send(name, *args)
+   ret = MutableInteger.new(ret) if ret.is_a?(Integer)
+   ret
  end
```

理想どおりに動くようになった

```ruby
x = MutableInteger.new(3)
y = x + 2

x.class #=> MutableInteger
y.class #=> MutableInteger

y.next! #=> 6
```

## 演算結果を `MutableInteger` にする part2

上記の対応を行っても、`y = 2 + x` のような計算を行うと、y が `Integer` になってしまう。

```ruby
x = MutableInteger.new(3)
y = 2 + x

x.class #=> MutableInteger
y.class #=> Integer

y.next! #=> NoMethodError (undefined method `next!' for 5:Integer)
```

`MutableInteger` を2項演算子の右側に置いた場合でも、`MutableInteger` のインスタンスを返すように修正する。
具体的には、`#coerce` メソッドをオーバーライドし、2項演算子の左側を `MutableInteger` に変換してやれば良い。


```diff
class MutableInteger

  # 中略

+ def coerce(other)
+   [MutableInteger.new(other), to_i]
+ end

  # 中略

end
```

理想どおりに動くようになった

```ruby
x = MutableInteger.new(3)
y = 2 + x

x.class #=> MutableInteger
y.class #=> MutableInteger

y.next! #=> 6
```

## 前置インクリメントの実装

rubyは単項演算子をオーバーライドできるので、それを利用する。

単項の `+` が、「同じ行から２回」呼び出されたらインクリメントを行う。

```diff
class MutableInteger
  def initialize(integer)
    raise "引数は整数にしてね" unless integer.is_a?(Integer)
    @component = integer
+   @callers = []
  end

+ def +@
+   @callers << caller
+   if @callers[-1] == @callers[-2]
+     @callers = []
+     return next!
+   end
+
+   @component = +@component
+   self
+ end

  # 中略

  def method_missing(name, *args)
    super unless @component.methods.include?(name.to_sym)

+   @callers = [] if name != :+@

    ret = @component.send(name, *args)
    ret = MutableInteger.new(ret) if ret.is_a?(Integer)
    ret
  end
end
```

同じ行からの呼び出しかどうか判定するため、 `Kernel.caller` を利用した。(生まれて初めて使った)
https://docs.ruby-lang.org/ja/2.5.0/method/Kernel/m/caller.html

ちなみに、「同じ行から２回」がトリガーなので、

```ruby
+x; +x
```

のような記述でもインクリメントされてしまうバグがある。
頑張ったが直せなかった。

## 完成！！

最終的に、以下のようなクラスとなった。

```ruby
class MutableInteger
  def initialize(integer)
    raise "引数は整数にしてね" unless integer.is_a?(Integer)
    @component = integer
    @callers = []
  end

  def +@
    @callers << caller
    if @callers[-1] == @callers[-2]
      @callers = []
      return next!
    end

    @component = +@component
    self
  end

  def coerce(other)
    [MutableInteger.new(other), to_i]
  end

  def next!
    @component = @component.next
    self
  end

  def to_i
    @component
  end

  def to_s(base = 10)
    @component.to_s(base)
  end
  alias inspect to_s

  def method_missing(name, *args)
    super unless @component.methods.include?(name.to_sym)

    @callers = [] if name != :+@

    ret = @component.send(name, *args)
    ret = MutableInteger.new(ret) if ret.is_a?(Integer)
    ret
  end
end
```


# 作った感想

単項演算子や `#coerce` メソッドのオーバーライド、`Kernel.caller` など、ふだん絶対に使わないものが使えたので楽しかった。出来上がったコードは、虚しく儚く、無足で無益な役立たずだが、その過程で `Integer` クラスの仕様をいろいろ勉強できたので、案外ネタコーディングも悪くないかも知れない。
まあ、今回得た知識が実務で役立つ場面は無いような気もするが。


# 最後に注意

※ 念のため言っておきますが、**この記事はあくまでネタ、ジョークなので、間違っても実用のプログラムにこんなクラスを仕込んではいけませんよ？**