特異クラスの特異クラスを使ってみたい

# TL; DR

- あるオブジェクトから、特異クラスの特異クラスに定義されたインスタンスメソッドは呼び出せない
- 特異クラスの特異クラスについても「スーパークラスの特異クラスは特異クラスのスーパークラス」

# はじめに

「メタプログラミングRuby」には、特異クラスについて以下のような記述があります。

> 特異クラスはクラスである。クラスはオブジェクトである。オブジェクトは特異クラスを持っている......。これはどこまで続くのだろう？他のオブジェクトと同じように、特異クラスも特異クラスを持っているはずだ。
> 特異クラスの特異クラスが役に立つ場面を見つけたら、ぜひ世界に発信しよう。
> (メタプログラミングRuby 第2版 P.130)

www.amazon.co.jp/dp/4873117437

前々から心に留まっていたこのコラム。そこで本記事では、特異クラスの特異クラスの使い方をひねり出してみました[^usage]。

:::note warn
私は業務等の大規模開発でRubyを使ったことがありません...「普通にこう書いたほうが楽」等ありましたらコメントしていただけるとありがたいです。
:::

# 動作を確かめる
まずは、特異クラスの特異クラスがどのように動くのかを確認します。検証したバージョンは以下の通りです。

```bash
$ ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux]
```

## 特異クラスの特異クラスに定義されたインスタンスメソッドは呼び出せない

オブジェクトは特異メソッド（＝特異クラスのインスタンスメソッド）を呼び出すことができます。

```ruby
str = "abc"

class << str
  def greet_singleton
    puts "singleton"
  end
end

# selfを使って書いても同様
def str.greet_singleton
  puts "singleton"
end

str.greet_singleton # singleton
```

「何を当たり前のことを」という話ですが、**特異クラスの特異クラスのインスタンスメソッド**になると話が変わってきます。

```ruby
str = "abc"

class << str
  class << self
    def greet_singleton_singleton
      puts "singleton_singleton"
    end
  end
end

# NoMethodError!
str.greet_singleton_singleton
```

もちろん、以下は可能です。

```ruby
str = "abc"

class << str
  class << self
    def greet_singleton_singleton
      puts "singleton_singleton"
    end
  end
end

# 特異クラスから特異クラスの特異クラスのインスタンスメソッドを呼ぶ
str.singleton_class.greet_singleton_singleton # singleton_singleton
```

「メタプログラミングRuby」に出てきたメソッド探索の仕組み「右へ一歩、それから上へ」で考えたら納得しました。（図中の `#` は特異クラスを表します）

![method_search.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/09fa14f2-ef0f-8d68-e40c-239ecaf803c2.png)

## スーパークラスの特異クラスは特異クラスのスーパークラス

「スーパークラスの特異クラスは特異クラスのスーパークラス」、こちらも「メタプログラミングRuby」本文に出てきたRubyのクラスの仕組みです[^meta]。

この関係は**特異クラスの特異クラスでも成り立ちました**。以下のように、`str`の特異クラスの特異クラスが `String` の特異クラスを継承していることが確認できます。

```ruby
# Stringの特異メソッド
puts String.try_convert("a")
# strの特異クラスからも呼び出される
puts str.singleton_class.try_convert("a")
```

図で書くと以下のようになります。

![superclass_singletonclass.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/7897f769-2389-312c-2abb-331a83ee4f12.png)

# 特異クラスの特異クラスを使ってみたい
## 特異メソッドの定義でメタプログラミングを使う
というわけで実際に特異クラスの特異クラスを使ってみます。1つ思いついたのは、特異クラスの定義内でDSLを使う場合です。

```ruby:こんなことをしたい
person = Person.new("Taro", 20)

class << person
  class << self
    include Debugger
  end

  # インスタンス変数をデバッグするメソッドを定義
  # (呼び出すとdebugメソッドが作られる)
  debug_vars :name, :age
end

person.debug
```

```
@name: "Taro"
@age: 20
```

`debug` を `person` の特異メソッドとして定義するためには、特異クラス内で特異メソッド `debug_vars` が使える必要があります。そのため、`debug_vars` を定義する場所は `person` の特異クラスの特異クラスになります。

```ruby
# 使いまわせるようモジュール化
module Debugger
  # 指定したインスタンス変数を表示するメソッド `debug` を定義するメソッド
  def debug_vars(*var_names)
    define_method :debug do
      var_names.each do |name|
        value = instance_variable_get("@" + name.to_s)
        puts "@#{name}: #{value.inspect}"
      end
    end
  end
end
```

正直使える場面は多くなさそうです... (大抵は設計をちゃんとしてクラスに組み込んだほうがよさそう)

# 特異クラスはどこまで作れるのか
最後に、「特異クラスの特異クラスの特異クラスの...」を限界までたどってみたいと思います。理論上限界はありませんが果たして...

```ruby
module SingletonTracer
  def trace_singleton(n)
    singleton = self
    n.times do
      singleton = singleton.singleton_class
    end
    singleton
  end
end

obj = Object.new
class << obj
  include SingletonTracer
end
```

手始めに100回。余裕ですね。

```ruby
puts obj.trace_singleton(100)
```

```
#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Class:#<Object:0x00007f2fe2dc4420>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
```

徐々に増やしていくと、2500回でスタックの限界に到達してしまいました。

```
puts obj.trace_singleton(2500)
(irb#1):39:in `to_s': stack level too deep (SystemStackError)
        from (irb#1):39:in `to_s'
        from (irb#1):39:in `to_s'
        from (irb#1):39:in `to_s'
        from (irb#1):39:in `to_s'
        from (irb#1):39:in `to_s'
        from (irb#1):39:in `to_s'
        from (irb#1):39:in `to_s'
        from (irb#1):39:in `to_s'
         ... 2319 levels...
```

（**2023/5/29追記**: 再度試してみたところ11887回まで成功しました。メモリ負荷をあげても結果が変わらず、当時の2500回で `SystemStackError` する状態は再現できませんでした...他にも要因があるのでしょうか...?）

~~表示しなければさらに入れ子を増やせますが、100000に増やしたらirbが固まってしまいました(それはそう)~~

[^usage]: ~~正直に言うと、昔思いついたネタをQiitaのイベントに便乗して放流しました~~
[^meta]: 初めて読んだ時にこの仕様の美しさに痺れました...