Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyのselfを徹底解説!場面別の挙動と初心者のつまずきポイント

Rubyを書いているなら、必ず意識することになるのがselfです。経験を積んだRubyistであれば、コードを読み書きするときにselfが常に頭の片隅にある感覚を持っているはずです。

しかし、Rubyの経験が浅い人にとっては、selfは謎だらけの存在です。常に変化しているのに、コード上には明示的に現れません。「知っているのが前提」とされているからです。

実は、初心者がつまずく問題の多くは、selfへの理解不足が原因です。インスタンス変数が「消えた」ように感じたり、mixinでどのデータが見えているのか分からなくなったりしたことはありませんか?それは、その文脈におけるselfを理解していなかったことが原因です。

この記事では、日常的なさまざまな場面におけるselfの挙動を、具体的なコード例とともに見ていきます。

selfとは何か?

「Rubyではすべてがオブジェクトである」という言葉を耳にしたことがあるでしょう。それが事実なら、あなたが書くすべてのコードは、何らかのオブジェクトに「属している」ことになります。

selfは、現在実行中のコードを「所有」しているオブジェクトを指す特別な変数です。Rubyはいたるところでselfを利用しています。

  • インスタンス変数(@myvar)の解決
  • メソッドや定数の探索(ルックアップ)
  • メソッド、クラス、モジュールの定義時

理論上はselfはとてもシンプルです。しかし実際には、厄介な状況に遭遇しがちです。だからこそ、この記事を書きました。

selfの具体例

これからいくつかの例を順番に見ていきます。最初の例が簡単すぎると感じても、そのまま読み進めてください。徐々に高度な内容になっていきます。

インスタンスメソッドの中

以下のコードでは、reflectはインスタンスメソッドです。これはGhost.newで作成したオブジェクトに属するため、selfはそのオブジェクト自身を指します。

class Ghost
  def reflect
    self
  end
end

g = Ghost.new
g.reflect == g # => true

クラスメソッドの中

次の例では、reflectGhostのクラスメソッドです。クラスメソッドの場合、そのメソッドを「所有」するのはクラス自身なので、selfはクラスを指します。

class Ghost
  def self.reflect
    self
  end
end

Ghost.reflect == Ghost # => true

モジュール内に定義した「クラスメソッド」でも同じことが起こります。

module Ghost
  def self.reflect
    self
  end
end

Ghost.reflect == Ghost # => true

Rubyではクラスもモジュールもオブジェクトとして扱われることを思い出してください。つまり、この挙動は最初のインスタンスメソッドの例と本質的には同じです。

クラス・モジュール定義の中

RubyがRailsのようなフレームワークと相性が良い理由のひとつは、クラスやモジュールの定義内で任意のコードを実行できる点にあります。定義内に書いたコードは、他のRubyコードと同じようにそのまま実行されます。唯一の違いはselfの値です。

以下のように、selfは定義中のクラスやモジュール自身を指します。

class Ghost
  self == Ghost # => true
end

module Mummy
  self == Mummy # => true
end

mixinされたメソッドの中

selfという観点では、mixinされたメソッドは通常のインスタンスメソッドやクラスメソッドとまったく同じように振る舞います。これは理にかなっています。そうでなければ、mixinがミックス先のクラスとやり取りできなくなってしまうからです。

インスタンスメソッドの場合

reflectメソッドはモジュール内で定義されていますが、そのselfはミックス先のクラスのインスタンスになります。

module Reflection
  def reflect
    self
  end
end

class Ghost
  include Reflection
end

g = Ghost.new
g.reflect == g # => true

クラスメソッドの場合

extendでクラスにクラスメソッドをmixinした場合も、selfは通常のクラスメソッドとまったく同じように振る舞います。

module Reflection
  def reflect
    self
  end
end

class Ghost
  extend Reflection
end

Ghost.reflect == Ghost # => true

メタクラス(特異クラス)の中

一度は目にしたことがあるかもしれません。複数のクラスメソッドを一気に定義できる、この人気のショートカット構文です。

class Ghost
  class << self
    def method1
    end

    def method2
    end
  end
end

class << fooという構文は実はかなり興味深いものです。これを使うと、オブジェクトのメタクラス(「シングルトンクラス」「特異クラス」とも呼ばれます)にアクセスできます。メタクラスについての詳細は別の記事で改めて取り上げる予定ですが、今は「メタクラスとは、特定のオブジェクトに固有のメソッドがRubyによって格納される場所である」ということだけ覚えておけば十分です。

class << fooブロックの中からselfにアクセスすると、メタクラスが得られます。

class << "test"
  puts self.inspect
end

# => #<Class:#<String:0x007f8de283bd88>

どのクラスにも属さないトップレベル

どのクラスにも属さない場所でコードを実行しても、Rubyはselfを提供してくれます。それは「main」と呼ばれる、Objectのインスタンスを指します。

puts self.inspect # => main

まとめ

selfの値は、コードがどこで実行されるかによって決まります。ポイントを整理すると以下の通りです。

  • インスタンスメソッド内 → レシーバーのオブジェクト
  • クラスメソッド内 → クラス自身
  • クラス/モジュール定義内 → 定義中のクラス・モジュール
  • class << self 内 → メタクラス(特異クラス)
  • トップレベル → main(Objectのインスタンス)

「今、selfは何を指しているのか?」を常に意識するだけで、インスタンス変数の消失やmixin絡みのバグの多くは未然に防げます。ぜひ日々のコーディングに活かしてみてください。

  1. Rubyのdupとcloneの違いを徹底解説!オブジェクト複製の基礎知識

    Rubyではオブジェクトをコピーできることをご存じでしょうか?しかも、コピー用のメソッドは2種類用意されています。 そのメソッドとは: dup clone それぞれの違いについては後ほど詳しく見ていきますが、まず最初に… そもそもなぜオブジェクトをクローンする必要があるのでしょうか? Rubyの多くのオブジェクトはミュータブル(変更可能)です。つまり、作成した後に内容を書き換えることができます。 元のオブジェクトはそのまま保持しつつ、内容を変更したい場合には、クローン(複製)を作成するのが有効です。 具体例を挙げてみましょう。 「先頭以外のすべての要素を含む配列」が欲しい場面を想定します。

  2. 【Ruby】デコレータデザインパターンでクラスを変更せずにオブジェクトを拡張する方法

    デコレータ(Decorator)デザインパターンとは、どのようなものなのでしょうか? また、このパターンをRubyのプロジェクトではどのように活用すればよいのでしょうか? デコレータデザインパターンを利用すると、クラスそのものを変更することなく、オブジェクトに新しい機能を追加して拡張することができます。 それでは、具体的な例を見ていきましょう! ログ出力とパフォーマンス計測の実装例 この例では、rest-clientのようなGemを使ってHTTPリクエストを送信します。 コードは次のようになります。 require restclient data = RestClient.get(www.