読者です 読者をやめる 読者になる 読者になる

メタプログラミングは念能力みたいに思えた

冬休みのシュクダイ

年末年始のお休みに突入するに際しまして、よちよち.rbの冬休みのシュクダイとして、メタプログラミングRubyを最後まで読んで当ブログに感想文を書くことを設定しました。

最初は、何日も休みがあるので余裕かなと思ってたんですが、結果、年末年始はイベント盛りだくさんで、もはや通常の土日の方が時間あるんじゃないのか、というレベルだったのですが、お休み最後の今日に何とか読み進めまして、ブログ記事を書く段階に至ることが出来ました。宣言するって大事ですね。機会を与えて頂いた、よちよち.rbに感謝です。

ただ、残念ながらまだ全部は読めておらず、第1部を読了しているだけなのですが、この時点でも充分内容は濃いので、良しとしています

メタプログラミングRuby

メタプログラミングRuby

書くこと

メタプログラミングRubyの第1部は、第1章〜第6章で構成されていて(第6章はエピローグの1ページだけですが)、第1章の月曜日から始まって、第5章の金曜日まで、職場で先輩プログラマと初心者プログラマメタプログラミングしていく物語になっています

これまで、月曜日(オブジェクトモデル)と火曜日(メソッド)は何回か読んでいたのですが、先に進めていませんでした。今回水曜日以降を読み進めてみて、月曜と火曜の話をさらに掘り下げるような話が繰り広げられて、大変ワクワクしました!

メタプログラミングというよりは、Rubyの仕組みなのかもしれないですが、驚きと凄さの連続に、たとえハンター試験に合格しても、これを知らないと本当のハンターにはなれないような気持ちになりました。改めて素晴らしい本ですね

各曜日それぞれに纏めていきたい気持ちもありますが、ボリュームがひどいことになるので、今回はその中でも木曜日(クラス定義)に書かれていた 特異クラス、特異メソッド について記述したいと思います

「特異クラス」という言葉の意味が分からない

「特異クラス」という言葉は聞いたことがあったのですが、特異?ってどんな状態なのか、全く想像出来てませんでした。正直、未だになぜ「特異」という日本語なのか良く分かっていません

「特異」を国語辞典で調べると

  • 特別に他とちがっていること。また、そのさま。

普通のクラスとは違うよって意味ですかね・・・

原文ではどう書かれているのか

  • (日本語)4.4.2 特異クラスの出現
  • (英語)Singleton Classes

Singletonって「1個の」ってことですよね・・・

特異・・・・・・なんだって?

こういうタイトルのコラムが書かれていました。英語はsingletonだったりeigenclassという表現だったりするようです。eigenclassは「オブジェクトそれ固有のクラス」という意味らしいです

自分なりの解釈

特異という言葉にはあまり深い意味はないのかな、と解釈しています

Class.ancestorsとかでは値として取得出来ないので、ちょっと違う、特異なクラスとして名付けられているだけで、実際の働きを教えてくれるような名前ではないのかなと思います

結構、「シングルトンクラス」と呼ばれていることも多い気もします

特異クラスとは

オブジェクトが裏に持っているクラス。意図的に表示させない限り、姿を現さないですが、オブジェクトの裏に出来てるみたいです

姿を確認するためには

obj = Object.new

eigenclass = class << obj
  self
end

とすれば良いと書いてありました。eigenclass.class => Classなので、特異クラスもクラスです

特異クラスの使い方

それで、特異クラスってどうやって使うのか、ということを理解するには、先に特異メソッドを定義してみるが良いと思います

特異メソッドの定義

これは、衝撃だったのですが、Rubyでは、

def (オブジェクト).(メソッド名)
  #  中身
end

という形で、そのオブジェクト固有のメソッドを定義出来ます

例えば、

greeting = 'hello'

def greeting.new_year
  'A Happy New Year'
end

greeting.new_year => "A Happy New Year"

というような形で、greetingの元々のクラスであるStringとは関係のないgreetingが固有に持つメソッドが定義出来るのです

月曜日の話との矛盾?

この特異メソッドの話を聞いて真っ先に思うのは、月曜日に学んだ

  • オブジェクトは複数インスタンス変数とクラスへのリンクで構成されている
  • オブジェクトのメソッドはオブジェクトのクラスに住んでいる

という内容です。オブジェクトはメソッドを持たないって言ってたのに、オブジェクト固有でメソッドを定義出来るってどういうことなのか、と憤りました

特異クラスの登場

メタプログラミングRubyでは先に特異メソッドが出てきて、後から特異クラスが出てきたのですが、結局のところ、この特異メソッドを定義する場所が特異クラスだ、ということみたいですね

オブジェクトと常にセットで特異クラスがいるので、特異メソッドは特異クラスに定義されます。オブジェクトでメソッドを実行するときには、まず自分の特異クラスを見て、その後、自分のクラス、親クラスと継承チェーンを昇っていきます

先ほどの例でいけば、まず、greetingの特異クラスを見たあと、Stringクラス、Comparableモジュールと継承チェーンを昇っていくので、new_yearメソッドが実行できます

特異クラスの親クラス

もう1つ疑問があって、特異クラスの親クラスは何なのか、ということです。

答えは、メタプログラミングRubyに記載されていて、先ほどのgreetingの例でいけば、Stringクラスが親クラスになります。

これは分かるような分からないような。。。

月曜日の継承チェーンではオブジェクトから自分のクラスへは右に進んで、そこからsuperclassに向かって上に昇っていたので、特異クラスを考慮する場合には、右下に行ってから上に昇る形なんですかね・・・

メタプログラミングRubyでは、特異クラスを追加した後のメソッド探索の図も追加されていましたが、そこでは、すぐ右にオブジェクトの特異クラスがあって、そこからsuperclassに上がっていました。(右下には行ってなかったです)

オブジェクトの高さが所属しているクラスよりも低くなっているので、ちょっと月曜日の話と違ってくる気もするのですが、まあこれはこのまま理解すれば良いかなと思います。

※この部分、図が書けなくて、分かりにくい話になっている気がします

クラスメソッドについての理解が深まる

特異クラス、特異メソッドの基本的な定義方法が分かった後、クラスメソッドの話に突入すると、なるほどの連続でした

クラスメソッドはそのクラスの特異クラスが持つ

これもメタプログラミングRubyの序盤に話があった気がしますが

  • クラスはClassクラスのオブジェクト(例えば、String.class => Class

ということを思い出すと、クラスもオブジェクトなので、特異クラスが存在することが分かります。

さらに、何となく丸暗記していたクラスメソッドの定義方法を思い出すと

class Hoge
  def self.fuga
    # 中身
  end
end

だったり、

class Hoge
  class << self
    def fuga
      # 中身
    end
  end
end

だったりして、「あ、これHogeクラスのスコープの中で特異メソッド定義してる!」と気付きます。

クラスメソッドってクラスの特異メソッド(特異クラスが持つメソッド)だったんだな、と感涙します。Classクラスに定義するではなく、各オブジェクトごとに固有で使うメソッドなわけですよね。

クラスの特異クラスの親クラス

先ほどと同じ形で考えると、Hogeクラスの特異クラスの親クラスはClassクラスかな、と思ったのですが、そうではないようです(じゅげむじゅげむ)

Hogeクラスの特異クラスの親クラスは、Hogeクラスの親クラスの特異クラスになります

これも、継承チェーンを考えれば理解は出来ます。いきなりClassとか行かれたら不便ですし、親クラスの特異クラスが持つメソッドが継承出来るので。なんというか、そりゃそうですよね、という感じです。

モジュールのメソッドをクラスメソッド

これも丸暗記していた部類ですが、モジュールで定義したメソッドをincludeするかextendするか、という違いも特異クラスが分かると理解が捗ります

例えば

module Fugafuga
  def fuga
    'Fuga'
  end
end

があったとして、何となく「クラスメソッドにするなら、extend」ぐらいに覚えてました

実際には、includeを使ってもクラスメソッドには出来て、

class Hoge
  class << self
    include Fugafuga
  end
end

というように、特異クラスを開いてそこにincludeすれば特異メソッドになります。

この作業を簡略にするために用意されているのがextendメソッドで、

class Hoge
  extend Fugafuga
end

Hoge.fuga => "Fuga"

と書くことが出来ます。includeはそのクラスに、extendはそのクラスの特異クラスに取り込むだと理解しました。なんとなく呪文のように覚えていたことが、腹落ちする瞬間でした。

年末年始の心残り

メタプログラミングRubyを最後まで読めなかったこともそうなのですが、メタプログラミングRubyの後にはRubyのしくみ -Ruby Under a Microscope-を読みたいと思っていて、年末年始で読むところまで行けなかったのが、大変残念です

メタプログラミングRubyを読んで、Rubyの仕組みには大変興味が強くなっていますので、出来るだけ早く取りかかりたいと思います(そしてその感想を当ブログに!)

Rubyのしくみ -Ruby Under a Microscope-

Rubyのしくみ -Ruby Under a Microscope-