UDAUDA.BLOG

うだうだしながらIT関連の記事を書いていきます!

メタプログラミング

1章

メタプログラミングとは、言語要素を実行時に操作するコードを記述することである

2章 オブジェクトモデル

オープンクラス

Rubyは既存のクラスを再オープンして、その場で修正できる。この技法をオープンクラスという。

class string
  def to_alphanumeric
    gsub(/[^\w\s], '')
  end
end

オープンクラスのダークサイド

何も考えずにクラスにコードを追加すると、元々あるメソッドを上書きしてしまい思わぬバグに繋がってしまう。こうしたクラスへの安易なパッチをモンキーパッチという。

オブジェクトモデルの内部

インスタンス変数

  • Rubyのオブジェクトのクラスとインスタンス変数には何の繋がりもない
  • 同じクラスのオブジェクトであってもインスタンス変数の数が異なることがある
  • インスタンス変数の名前と値はハッシュのキーとバリューのようなもの

メソッド

  • インスタンス変数の他にオブジェクトはメソッドを持っている
  • ほとんどのオブジェクトはObjectクラスから多くのメソッドを継承している
  • Objを中身を見ると、メソッドがなくインスタンス変数とクラスへの参照があるだけ(全てのオブジェクトはクラスに属しているから。オブジェクト指向的に言えば、全てのオブジェクトはクラスのインスタンスだから。)

クラスの真相

  • Rubyのオブジェクトモデルを学ぶときに最も重要なのは「クラスはオブジェクト」ということ
  • クラスはオブジェクトなので、オブジェクトに当てはまるものはクラスに当てはまる
  • オブジェクトと同じように、クラスにもクラスがある。
  • クラスのメソッドはClassクラスのインスタンスメソッドである。

モジュール

  • ClassクラスのスーパークラスはModule
  • 全てのクラスはモジュールである。正確にいうとクラスはオブジェクトの生成やクラスを継承するための3つのインスタンスメソッド(new, allocate, superclass)を追加したモジュール
  • クラスとモジュールは密接に結びついているため、Rubyは同じ役割を担う「モノ」と見なすことができる。
  • モジュールとクラスを分ける理由はどちらかを選択することによりコードが明確になるから

定数

  • 大文字で始まる参照は、クラス名やモジュール名も含めて全て定数。
  • プログラミにある全ての定数は、ファイルシステムのようにツリー状に配置されており、モジュールがディレクトリで、定数がファイル
  • ファイルシステムと同じようにディレクトリが違えば同じ名前のファイルを複数持つことができる
  • 定数のパスは::で区切る

オブジェクトとクラスのまとめ

  • オブジェクトとはインスタンス変数の集まりにクラスへのリンクがついたもの
  • オブジェクトのメソッドはオブジェクトではなくオブジェクトのクラスに住んでいてクラスのインスタンスメソッドと呼ばれる
  • ClassクラスはModuleクラスのサブクラスであり、クラスもモジュールである
  • Classクラスにはインスタンスメソッドがある。通常のオブジェクトと同じようにクラスもnewなどのメソッドを持っている

メソッドを呼び出すときに何が起きている?

メソッドを呼び出すときRubyは以下の2つを行う - メソッドを探す(メソッド探索) - メソッドを実行する(selfと呼ばれるものが必要)

メソッド探索

  • メソッドを呼び出すときにはRubyはオブジェクトのクラスを探索してメソッドを発見している
  • レシーバーは呼び出すメソッドが属するオブジェクトのこと
  • 継承チェーンはあるクラスの継承先をBasicObjectまで続けた道筋
  • メソッド探索を一言でまとめると「Rubyがレシーバのクラスに入り、メソッドを見つけるまで継承チェーンを上がること」

モジュールとメソッド探索

  • 継承チェーンにはスーパクラスに向かって進んでいくが、継承チェーンにはモジュールも含まれる
  • モジュールをクラスにインクルードするとRubyはモジュールを継承チェーンに挿入し、それはインクルードするクラスの真上に入る
  • 同じモジュールを2回挿入した時は2回目の挿入を無視する
  • Kernelモジュールは全てのクラスでインクルードされており、ppやprintなどのメソッドをKernelモジュールで定義している

メソッドの実行

selfキーワード

  • Rubyのコードはオブジェクト(カレントオブジェクト)の内部で実行される。カレントオブジェクトはselfと呼ばれる
  • selfの役割を担えるオブジェクトは同時に複数は存在しない。
  • メソッドを呼び出す時はメソッドのレシーバがselfになる。その時点から全てのインスタンス変数はselfのインスタンス変数になり、レシーバを明示しないメソッド呼び出しは全てselfに対する呼び出しになる。レシーバを明示しないメソッド呼び出しは全てselfに対する呼ぶ出しになる。

クラス定義とself

  • クラスやモジュールの定義の内側(メソッドの外側)ではselfの役割はクラスやモジュールそのものになる

3章 メソッド

動的メソッド

  • 通常は.to_sのようにメソッドを呼び出すが、同じことをsendメソッドを使ってできる。
  • sendメソッドを使用することで呼び出すメソッドを動的にできる

メソッドを動的に定義する

  • define_methodを使用すれば、メソッドをその場で定義できる。それにはメソッド名とブロックを渡す必要があり、ブロックがメソッドの本体になる。
  • defキーワードではなく、Module#define_methodを使う理由はdefine_methodを使用すれば実行時にメソッド名を決定できるから。

method_missingを使う

  • method_missingはクラス、そのクラスのスーパークラスにメソッドがないとき呼び出される
  • これを利用して同じようなメソッドを何回も書くのではなくmethod_missingをオーバーライドしフックとすることでまとめることができる

4章 ブロック

ブロックの基本

  • ブロックは{}do...endキーワードで定義できる
  • ブロックを定義できるのはメソッドを呼び出すときだけ
computers.each do |computer|
~
end

ブロックはクロージャ

  • ブロックはどこにでも存在できるコードではなく、ブロックのコード単体では実行できない
  • ブロックのコードを実行するにはローカル変数、インスタンス変数、selfといった環境が必要。これらはオブジェクトに紐づられた名前のことで束縛と呼ばれる
  • つまり、ブロックとはコードと束縛をまとめ実行の準備をするもの
  • ブロックからは呼び出されるメソッドで定義された変数は見ることができない。これらの特性があるためブロックはクロージャであるというコンピュータ科学者もいる

スコープ

  • ある変数や関数が特定の名前で参照される範囲のこと
    • プログラムがスコープを変えると新しい束縛と置き換えられる

スコープゲート

  • プログラムがスコープを切り替えて新しいスコープをオープンする場所は3つある
    • クラス定義
    • モジュール定義
    • メソッド
  • これらの3つの境界線はclass, module, defといったキーワードが印付けられており、この3つのキーワードはスコープゲートとして振る舞う

スコープのフラット化

  • スコープゲートをメソッド呼び出しに置き換えると、他のスコープの変数が見えるようになる
  • これを入れ子構造のレキシカルスコープと呼んだり、「スコープのフラット化」と読んだりもする。後者は2つのスコープを一緒の場所に押し込めて、変数を共有するという意味。

クロージャのまとめ

  • Rubyのスコープには多くの束縛がある。スコープはclass, module, defといったスコープゲートで区切られる
  • スコープゲートを飛び越えて束縛にこっそり潜り込みたい時はブロックを使う。ブロックとはクロージャである
  • classはClass.newと、moduleはModule.newと, defはModule.define_mothodと置き換えることが可能。これをフラットスコープと呼ぶ
  • 同じフラットスコープに複数のメソッドを定義してスコープゲートを守ってやれば束縛を共有でき、これを共有スコープと呼ぶ

instance_eval

  • instance_evalに渡したブロックはレシーバをselfにしてから評価されるので、レシーバのprivataメソッドや@vなどのインスタンス変数にもアクセスできる
class Myclass
  def initialize
    @v = 1
  end

  obj = MyClass.new
  
  obj.instance_eval do
    self   # => <MyClass:>
    @v    # => 1
end

クリーンルーム

  • ブロックを評価するためだけにオブジェクトを生成することもある。
  • クリーンルームとはブロックを評価する環境のこと。

呼び出し可能オブジェクト

  • ブロックの使用は2つのプロセスに分けられる
  • まずはコードを保管すること
  • それからブロックを(yieldを使用して)呼び出して実行する
  • 「コードを保管しておいて、後で呼び出す」方式はブロックだけに限らず、Rubyでコードを」保管できるところは少なくても3つある
    • Procのなか。これはブロックがオブジェクトになったもの
    • lambdaのなか。これはProcの変形
    • メソッドのなか。

Procオブジェクト

  • Rubyではほぼ全てがオブジェクトだが、ブロックは違う
  • Procはブロックをオブジェクトにしたもの。Procを生成するには、Proc.newをブロックに渡す
  • オブジェクトになったブロックをあとで評価するにはProc#callを呼び出す
ques = Proc.new { |x| x + 1 }
qies.call(2) # => 3
  • RubyではブロックをProcに変換する2つのカーネルメソッド、lambdaとprocが提供されている。

「Proc」対「lambda」

  • Procとlambdaの違いは2つある
    • returnキーワードに関すること
      • lambdaのreturnは単にlambdaから戻るだけ
      • Procの場合はProcが定義されたスコープから戻る
    • 引数チェックに関すること(引数の数が違う時の挙動の違い)   - lambdaは引数の数が違う時「ArgumentError」を出力する
      • Procは引数列を期待に合わせてくれる

Methodオブジェクト

  • メソッド自体もMethodオブジェクト

呼び出し可能オブジェクトのまとめ

  • ブロック(オブジェクトではないが呼び出し可能):定義されたスコープで評価
  • Proc:Procクラスのオブジェクト。ブロックのように定義されたスコープで評価
  • lambda:これもProcクラスのオブジェクトだが、通常のProcとは少し違う。ブロックやProcと同じくクロージャであり、定義されたスコープで評価される
  • メソッド:オブジェクトに束縛され、オブジェクトのスコープで評価される。オブジェクトのスコープから引き離し、他のオブジェクトに束縛することも可能

クラス定義 5章

  • クラスは高機能なモジュールである

クラス定義の中身

  • クラス定義のことはメソッドを定義する場所だと思っているかもしれない。実際にはクラス定義にはあらゆるコードを書くことができる。
class MyClass
  puts 'Hello'
end

=> Hello

  • クラス(やモジュール)定義の中ではクラスがカレントオブジェクトselfになる
  • クラスとモジュールは単なるオブジェクトであり、クラスもselfになることが可能

カレントクラス

  • Rubyのプログラムは常にカレントオブジェクトselfを持っている。それと同様に常にカレントクラス(あるいはカレントモジュールを持っている)
  • メソッドを定義するとそれはカレントクラスのインスタンスメソッドになる
  • カレントクラスを参照するキーワードはないが、コードを見ればわかる
    • プログラムのトップレベルではカレントクラスはmainのクラスのObjectになる
    • classキーワードをクラスをオープンにすると、そのクラスがカレントクラスになる
    • メソッドの中ではカレントオブジェクトのクラスがカレントクラスになる

カレントクラスのまとめ

  • Rubyインタプリタは常にカレントクラスの参照を追跡している。defで定義された全てのメソッドはカレントクラスのインスタンスメソッドになる
  • クラス定義の中では、カレントオブジェクトselfとカレントクラス(定義しているクラス)は同じである
  • クラスへの参照を持っていれば、クラスはClass_evalでオープンできる

クラスインスタンス変数

class MyClass
  @my_var = 1
  def self.read; @my_var; end
  def write; @my_var = 2; end
  def read; @my_var; end
end

obj = MyClass.new
obj.read  #=> nil
obj.read
obj.write
obj.read  #=> 2
My.Class.read # =>1
  • 上の@my_varはそれぞれ異なるスコープで定義されており、別々のオブジェクトに属している
  • クラスが単なるオブジェクトであることと、プログラムの中のselfを追跡することを思い出す必要がある
  • 1つ目の@my_varはobjがselfとなる場所に定義されており、objオブジェクトのインスタンス変数
  • 2つ目の@my_varはMyClassがselfとなる場所に定義されており、これはMyClassというオブジェクトのインスタンス変数。クラス変数とも呼ばれる

特異メソッド

  • Rubyでは特定のオブジェクトにメソッドを追加できる。単一のオブジェクトに特化したメソッドのことを特異メソッドと呼ぶ。
class Movie
  def i_method
    p 'instance method'
  end
end

obj1 = Movie.new
obj2 = Movie.new

def obj1.s_method
  p 'singlton method'
end

obj1.i_method #=> 'instance method'
obj2.i_method #=> 'instance method'
obj1.s_method #=> 'singlton method'
obj2.s_method #=>  undefined method `s_method' for #<Movie:0x00007fc2fc842c38> (NoMethodError)

クラスメソッドの真実

  • クラスは単なるオブジェクトであり、クラス名は単なる定数
  • クラスメソッドはクラスの特異メソッド

特異クラス

  • 通常のクラスとは別にオブジェクトは裏に特別なクラスを持っている
  • それがオブジェクトの特異クラスと呼ばれるもの(メタクラスやシングルトンクラス)
  • 特異クラス(シングルトンクラス)はインスタンスをひとつしかもてない
  • 継承ができない
  • 特異クラスはオブジェクトの特異メソッドが住んでいる場所だ
  • 特異メソッドを定義するとそのオブジェクトの特異クラスが作成されるため、特定のオブジェクトでしかメソッドが実行できない

qiita.com

大統一理論

  • オブジェクトは1種類しかない。それが通常のオブジェクトかモジュールになる
  • モジュールは1種類しかない。それが通常のモジュール、クラス、特異クラスのいずれかである
  • 全てのオブジェクトは「本物クラス」を持っている。それが通常のクラスか特異クラスである
  • 全てのクラスは(BasicObjectを除いて)一つの祖先(スーパークラスかモジュール)を持っている。つまり、あらゆるクラスがBasicObjectに向かって1本の継承チェーンを持っている
  • オブジェクトの特異クラスのスーパークラスは、オブジェクトのクラスである。クラスの特異クラスのスーパークラスはクラスのスーパークラスの特異クラスである
  • メソッドを呼び出す時はRubyはレシーバの本物のクラスに向かって「右へ」進み、継承チェーンを「上へ」進む。

メソッドラッパー