kansiho's blog

ruby, python, javascript. Rails, wordpress, OpenCV, heroku...

メタプログラミングを超ていねいにまとめる【第1章:オブジェクト指向、インスタンス変数、メソッド、定数、モジュール、クラスのメタプロ的な捉え方】

メタプログラミングとは何か

メタプログラミングという本をご存知だろうか。

メタプログラミングRuby

メタプログラミングRuby

Rubyを勉強し始めたときに、色んな人に薦められて読んだ本。これを読むと、rubyの全体観を把握して、特徴をつかむことができます。 今回は、rubyの認定試験などを受けるにあたって、復習として精読して見ようと思います。 章ごとにまとめる予定です。今回はスタートの第1章。

コードを記述するコードを記述すること

例1) ActiveRecord::Base

class Movie < ActiveRecord::Base

といった形でActiveRecordクラスを承継することで、

Database.sql "INSERT INTO  #{@table} (id) VALUES (#{@ident})"

なんてSQL文を書く必要もなく、

movie = Movie.create
movie.title ="博士の異常な愛情"
movie.save

といったメソッドが使用可能になる。 ActiveRecordは、オブジェクトとデータベースのマッピングを行うRubyの有名なライブラリ。 この場合のtitleメソッドのようなアクセサメソッドも、ActiveRecordがデータベースのスキーマから名前を取得して自動的にメソッドを定義してくれる。

例2) オープンクラスとモンキーパッチ

アルファベットとスペースを残して、?!といった特殊文字列を削除する機能をもったプログラムを考える。

def to_alphanumeric(s)
  s.gsub /[^\w\s]/, ""
end

しかしながら、メタプロマスターに言わせれば、「このメソッドはオブジェクト指向的じゃないね」。

class String 
  def to_alphanumeric
    gsub /[^\w\s]/, ""
  end
end

メタプロマスター的回答は、標準のStringクラスをオープンにし、そこにメソッドを加えること。 to_alphanumericはすべての文字列が持っていて問題のない汎用的な機能であったため、このような形を取ったのだ。

「クラスを定義するコード」は何だか特別な感じがするが、その他のコードと実際違いはない。

たとえば

3.times do
  class C
    puts "hello"
  end
end

#=> 
hello
hello
hello

ということもできる。 最初の1回の呼び出しえクラスが定義され、はじめてCが存在する。2,3回目は既存のクラスを再オープンしている。 我々は既存のクラスをいつでも再オープンして、修正できる。それには既存のクラス(StringやArrayといった)も含まれる。 この技術をオープンクラスという。 gemライブラリではオープンクラスがよく使われる。

オープンクラスの問題点:モンキーパッチ

うっかり、既存クラスに新しいメソッドを追加したつもりが、すでに定義されていたメソッドと同名にしてしまい、元のメソッドに依存しているために動作が変になるということがある。このようなクラスへの安易なパッチは「モンキーパッチ」という蔑称で呼ばれる。

自分の新しく定義しようとしているメソッドが存在するか確認するためには、たとえばArrayクラスなら [].methods.grep /^method_name/ というように、methodsでメソッド一覧を取得して、サーチすると良い。

クラスの真実

オブジェクト、クラス、定数に関する説明をする章。

インスタンス変数

class MyClass
  def my_method
    @v = 1
  end
end

obj = MyClass.new
obj.class #=> MyClass

この時点では、@vは存在しない。

obj.my_method
obj.instance_variables #=> [:@v]

my_method()を呼び出すと、存在しだす。

rubyインタプリタでinstance_variablesメソッドで調べて確認してみる。

f:id:serendipity4u:20170122152721p:plain

メソッド

ほとんどのオブジェクトはObjectクラスから多くのメソッドを継承しているため、メソッド一覧はとても長いものになる、以下のように。 f:id:serendipity4u:20170122153015p:plain

※ 以下に、私がQiitaにrubyオブジェクト指向をちょっとキャピキャピしたテンションでまとめた記事があるので、良かったらよんでください。

qiita.com

メソッドの名称を区分する:インスタンスメソッド

MyClass.my_methodではなく、MyClass.new.my_methodであること(オブジェクトに対して利用するメソッドであること)を伝えるため、 前者をクラスメソッド、後者をインスタンスメソッド」 と呼ぶ。

インスタンス変数はオブジェクトに住んでおり、オブジェクトごとに値が変わりうる点に注意。

クラスだってオブジェクトである

オブジェクトと同じように、クラスにもクラスがある。

"hello, I'am string".class #=> String
String.class #=> Class

といったように。 クラスのメソッドはClassクラス(ややこしい!)のインスタンスメソッドでもあるということ。 superclassメソッドでそのクラスのスーパークラスを知ることが出来る。

  String.superclass #=> Object
  Numeric.superclass  #=> Object
  Object.surperclass #=> BasicObject
  BasicObject.surperclass #=> nil

すべてのクラスはObjectクラスを継承しており、ObjectクラスはBasicObjectクラスを継承している。 そしてBasicObjectクラスがクラス階層のルートであることがわかった。

結局、クラスとは、new(), allocate(), superclass() メソッドを追加したモジュールに過ぎないのである(※allocateとは、newメソッドを補助するもので新しいオブジェクトを作る。newメソッドとは異なり、initializeメソッドの呼び出しは行わない)。

モジュールとクラス、なんで2種類あるわけ? 一緒にすればよかったんじゃないの?

ここまでで、「なぜじゃあモジュールとクラスが二つ用意されているのか」疑問に思う人もいるかもしれない(思った。)

メインの理由は

使い分けして意図を明確にできること
  • モジュール: ネームスペース、インクルードされるだけのコード
  • クラス: インスタンスを生成したり継承したりするもの

らしい。

定数

大文字で始まる参照は全て定数である。クラス名やモジュール名も含む。

定数はファイル、モジュール(or クラス)はディレクトリのようなもので、ファイルシステムと同じように、ディレクトリが違えば同じ名前のファイル(定数)を複数持てる。

module M
  X = "定数その1"
  class C
    X = "定数その2"
  end
end

M::X #=> 定数その1
M::C::X #=> 定数その2

のように。

モジュールと同じ名前のクラス名は使えないため、クラス名がモジュールと衝突したら、そのクラスを別のモジュール名で挟んむ(=ネームスペースで囲む)などの対応を取る。