メタプログラミングRuby読みメモ 3

4章 ブロック

  • ブロックはスコープを制御するのに強力なツール
  • ブロックは「呼び出し可能オブジェクト」
    • Proc や lambda などが含まれる

ブロックの基本

  • ブロックは、波括弧またはdo...endキーワードで定義できる
    • 1行のブロックには波括弧、複数行のブロックにはdo...endを使うという習慣がある
  • ブロックを定義できるのはメソッドを呼び出すときだけ
    • ブロックはメソッドに渡され、メソッドはyieldキーワードを使ってブロックをコールバックする
  • ブロックは引数を受け取ることもできる
    • ブロックをコールバックするときに、メソッドを呼び出すときと同じように引数を渡せる
  • メソッドの内部でKernel#block_given?メソッドでブロックの有無を確認できる

ブロックはクロージャ

  • ブロックのコードは単体では実行できない
    • ローカル変数、インスタンス変数、selfといった環境が必要
    • これらはオブジェクトに紐づけられた名前のことで、束縛とも呼ばれる
  • ブロックにはコードと束縛の両方が含まれる
  • ブロックを定義すると、その時点その場所にある束縛を取得する
  • ブロックをメソッドに渡したときにその束縛も一緒に連れて行く
  • ブロックの中で新しい束縛を定義することもできるが、ブロックが終了した時点で消える
  • このような特性からブロックはクロージャとも呼ばれる

スコープ

  • Ruby のスコープはきちんと区別されている
  • 新しいスコープに入ると以前のスコープは見えなくなる
  • メソッドから同じオブジェクトにある他のメソッドを呼び出すと、インスタンス変数は変わらない
  • グローバル変数はどのスコープからでもアクセスできる
  • トップレベルのインスタンス変数は、他のオブジェクトがselfになるまで呼び出せる

スコープゲート

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

  • class の代わりに Class.new で定義するとスコープを切り替えずに定義できる

  • def の代わりに define_method を使うとスコープを切り替えずにメソッドを定義できる

instance_eval

  • BasicObject#instance_evalに渡したブロックはレシーバをselfにしてから評価される
  • instance_evalに渡したブロックのことをコンテキスト探査機と呼ぶ
  • これを用いて、irbからオブジェクトの中身を見たいときに使うことができる

クリーンルーム

  • ブロックを評価するためだけにオブジェクトを生成することもある
  • このようなオブジェクトをクリーンルームと呼ぶ
  • 衝突を避けるためメソッドやインスタンス変数は増やさない方がいい

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

Proc オブジェクト

  • Ruby ではほぼ全てがオブジェクトだがブロックはオブジェクトではない
  • ブロックを保管しておいて後で実行したいときにオブジェクトが必要
  • Proc はブロックをオブジェクトにしたもの
  • オブジェクトになったブロックを評価するにはProc#callを呼び出す
  • Proc は遅延評価である
  • ブロックをProcに変換するカーネルメソッドはlambdaとprocと2つ用意されている

&修飾

  • ブロックはメソッドに渡す無名引数のようなもの
  • 通常はyieldを使って実行するが、yieldでは足りないケースが2つある
    • 他のメソッドにブロックを渡したいとき
    • ブロックをProcに変換したいとき
  • メソッドの引数列の最後に&修飾した名前を置くとブロックを束縛できる
  • これはProcとして変換されている

lambda

  • lambda で作られた Proc は他の Proc とは違う
  • Proc#lambda?で確認できる
  • lambda の return は lambda から戻るだけだが、Proc での return は Proc が定義されたスコープから戻る
  • lambda の方が Proc よりも引数の値に厳しい
    • Proc は引数の数が多いと切り落とし、少ないとnilを入れてくれる
    • lambda では ArgumentError となる

Method オブジェクト

  • Object#methodでメソッドそのものをオブジェクトとして取得できる
  • to_proc で Proc に変換できる
  • lambda は定義されたスコープで評価されるが、Methodは所属するオブジェクトのスコープで評価される

UnboundMethod

  • 元のクラスやモジュールから引き離されたメソッド
  • Method#unbindでMethodから変換できる
  • instance_methodを呼び出せばUnboundMethodを直接取得できる
  • UnboundMethodを呼び出すことはできないが、ここから通常のメソッドを生成することができる
    • UnboundMethod#bindで束縛することができる
    • ただし、元のクラスと同じクラス、もしくはサブクラスにしか束縛できない