『メタプログラミングRuby』 学習メモ

Part 1 meta programming Ruby

Ch01 initial M

  • introspection
    • Active Recordがintrospectionを使っている
  • meta programming: 言語要素を実行時に操作するコードの記述
    • codeを記述するcodeを記述
  • meta programmingの学習により,Rubyのruleがわかる

Ch02 Monday, Object model

  • すべての言語要素はObject model内に共存する
  • Open Class
    • classはscope 演算子のようなもの
    • monkey patchは×
  • Object modelの内部
    • Objectの中身
      • instance variable
        • 値が代入されたときに初めて出現
      • method
        • ObjectではなくClassに存在する.
          • 共通のClassを持つObjectはメソッドも共通
        • → instance method: Classに定義されていて,呼び出すにはObject(instance)が必要になるmethod
          • ←→ method: Object(instance)がもつmethod
    • Classの真相
      • Class.superclass # => Module
      • Class: Objectの生成やClassを継承するための3つのinstance methodを追加したModule
      • Moduleはinclude, Classはinstanceの生成や継承
      • Classの参照も保持できる.ClassはObject,Class名は定数
    • 定数
      • file systemと類似
      • Rakeの例: moduleで定数をまとめる例
    • まとめ
      • Object: instance variableの集まりに,クラスへのリンクがついたもの
      • Classについて
    • load, require
      • load: codeの実行のため.呼び出すたびにファイルを実行する.
        • 定数の扱いのための引数
      • require: libraryのimportのため.1回だけファイルを読み込む.
  • Object, Module, Classの関係について
    • 循環したり自己参照したりしている.
  • method call
    • method探索 + method実行
    • method 探索
      • receiverと継承チェーン
      • one step to the right, then up
      • Moduleも継承チェーンに入る.
        • includeは上に,prependは下にModuleを挿入
      • include, prependは1度目のみ有効
      • Kernel: ObjectがKernelをinclude
    • method 実行
      • receiverの参照を持つ
      • self, top level
      • private: explicit receiver不可
    • Refinements
      • monkey patchの回避.
      • usingで有効にする
      • 発展途上なので,直感に合わない挙動がある
  • 絡み合ったModule
    • 継承チェーンの確認
  • まとめ

Ch03 Tuesday, method

  • boiler plate methodの回避
  • 重複問題
  • dynamic method
    • dynamic dispatch: sendを使うことで,method nameをargumentにしてdynamicにcallできる
    • :(name) ← symbol
      • immutable. String: mutable.
      • immutableなので,メソッド名などのmeta programmingに関係する名前に使う
    • Pryの例
    • methodをdynamicにdefine
      • 実行時にmethod nameを決定するため,defではなくModule#define_methodを使う
      • Stringと変換可能
    • e.g.
      • Array#grep → define_method
  • method_missing
    • ghost method: method_missingのoverride
      • Hashieのe.g.
    • dynamic proxy: ghost methodを補足して,ほかのObjectに転送するObject
      • Gheeのe.g.
        • GitHubのObjectをdynamic hash に保持.hashのattributeは,ghost methodの呼び出しでアクセスできる
        • hashをProxy Objectでラップ.
    • respond_to_missing?もあわせてoverride必要
    • const_missing
      • 古いクラス名が呼び出されたときに,警告を出して新しいクラス名を返す
    • 受付可能な引数以外はBasicObject#method_missingを呼ぶ必要がある
  • blank slate
    • 最小限のmethodしか引き継がないClass
    • BasicObjectの継承.指定しなければObject.
    • methodの削除
      • Builderのe.g.
  • まとめ
    • Rubyのdynamicな能力により,複数のmethodに共通した外部の重複を排除できる
    • 可能であればdynamic method, 仕方なければghost method

Ch04 Wednesday, block

  • not from Object Oriented Design, but from Functional Programming Language like LISP
  • block day
    • blockの基本
      • do...end, {}
      • yieldでblockを呼び出す
  • Kernel methodで,usingのように例外の有無によらず処理を呼び出せるようにする振る舞いを追加できる
  • block is closure
    • blockは,コードと束縛の集まりの両方を含む
    • scope
    • scope gate: scopeを切り替えて,新しいscopeをopenする場所
      • class, module, def
      • defは呼び出したときに実行.class, moduleはすぐに実行
      • scopeのflatten, flat scope
      • shared scope
  • instance_eval
    • instance_evalに渡したblock: context 探査機
    • instance_execなら引数で渡すことができる
        - replace scope gate to method call
      
    • encapsulateの破壊
      • irb, test
      • Padrinoのe.g.
    • clean room: blockを評価するためだけのObject
  • callable Object
    • blockの使用の2process
      • codeの保管 → blockを呼び出して実行
    • block以外のコードを保管できるところ
      • Procのなか.← blockがObjectになったもの.
      • lambdaのなか. ← Procの変形.
      • methodのなか.
    • Proc Object
      • あとで評価
      • lambdaとprocで生成できる
      • ->lambda
      • &修飾
        • methodに渡されたblockを受け取って,それをProcに変換
        • Procをblockに戻すときも&修飾
      • HighLineのe.g.
        • あとで評価
    • Proc vs lambda
      • return
        • lambdaはlambdaから戻るだけ.Procは,Procが定義されたscopeから戻る
      • 項数(引数の数)
        • lambdaは一般的に厳しい.Procは合わせてくれる.
      • lambdaはmethodに似ていて,一般的にはlambdaを使う.
    • Method Object
      • blockやlambdaに似ているが,所属するObjectのscopeで評価
      • UnboundMethod
        • Active Supportのe.g.
        • 自分ではやらない方が〇
  • DSLを書く
    • event間で共有
    • global variableの削除
    • clean roomの追加
  • まとめ

Ch05 Thursday, Class definition

  • RubyのClass definitionはcodeを実行する
  • Classは高性能のModule. Class definitionに関することはModule definitionにも置き換えられる.
  • Class definitionの分かりやすい説明
    • Class, Moduleも単なるObjectで,selfになれる.
    • current Class
      • class_eval
        • self, current Classを変更する → Classを再open可能
        • flat scope
        • class_execもある
        • instance_evalとの使い分け.
    • Class instance variable
      • Class instance variableとObjectのinstance variableは別物
      • Class variableは△.共有の度合いが大きい.
      • testでのClass instance variableの使用
  • Class のtaboo
    • Class.new()で,Class keywordを使わずにClass作成
  • 特異method
    • 特定のObjectにmethodを追加できる.
    • Duck typing
    • Class methodはClassの特異method
    • Class macro
      • Module#attr_*のe.g.
        • attr_accessorなど
      • Module Classに定義されているので,selfがModuleでもClassでも使えるもの.
        • Class定義の中で使える単なるClass method
  • 特異Class
    • a.c.a. meta Class, singleton Class
    • 変わった構文を使わないとみることができない.
    • only one instance( so that called singleton Class )
    • Objectの特異methodの在り処
      • 特異methodがあれば,特異Classから探索する.特異ClassがObjectのsuper Classになる.
    • 継承
      • metaの2乗
        • 特異Classの特異Class
      • 大統一理論
        • Object, Module, Methodはそれぞれ1種類だけ.
        • Objectの特異Classのsuper Classは,ObjectのClass
        • Classの特異Classのsuper Classは,Classのsuper Classの特異Class
      • Class methodの構文
        • Class methodはClassの特異Classにある特異method
      • instance_evalは,current Classをreceiverの特異Classに変更する
        • 意図としては,selfの変更で良い.
      • Classのattributeの追加のe.g.
  • Class拡張
    • Object拡張も〇
    • どちらもObject#extendを使うのが〇
  • method wrapper
    • methodの再定義: 新しいmethodを定義して,元のmethodに名前を付ける
    • Thorのe.g.
      • around alias
    • Refinementsでaround alias
    • Prepend wrapper
  • 計算methodをaround aliasで変更することもできるというe.g.
  • まとめ

Ch06 Friday: codeを記述するcode

  • meta programmingの新しいtrickと,様々なtrickを組み合わせて難しいcoding課題を解決する方法を見る
  • Kernel#eval
    • REST Clientのe.g.
      • code文字列は文字列の代わりになるものを使うことが多い
    • 外部ソースにある任意のcode文字列を評価するときも使う
    • Binding Object
      • scopeをObjectにまとめたもの
      • eval "@x", b ← b: Binding
      • TOPLEVEL_BINDING
      • Pryの例
    • irbのe.g.
    • code 文字列 vs block
      • 基本はblockを使うべき
    • evalのproblem
      • code injection
      • 代用: dynamic dispatch
      • Object taintとsafe level
    • ERBのe.g.
      • safe levelがあるときは, sandboxが作られてそこで実行される.
    • load, requireとの比較
  • evalでの実装とevalの置き換え
    • classのscopeに入るためにclass_eval → 実行時まで分からないのでdynamic method
    • instance variableの読み書き: Object#instance_variable_set(/get)
  • blockでの置き換え
  • Kernel methodをClass macroに変更
    • あらゆるClass定義で使うために,ModuleまたはClassのinstance methodを作成する.
      • → class_evalも不要になる
  • hook method
    • Class#inheritedなどのmethod.defaultでは何もしない
    • 特異methodにはsingletonをつけないと動作しない
    • standard methodにplugin
      • superで元のincludeを呼び出す必要ある
    • VCRのe.g.
      • instance methodを特異Classにmix in → Class methodを追加できる
  • Class macroから,includeしたClassだけaccessできるように制限する
    • Moduleのincludedを上書きして,ClassMethodsを拡張する
  • まとめ

Ch07 epilogue

  • meta programmingも単なるprogramming

Part2 Railsにおけるmeta programming

Ch08 Rails tourの準備

  • Ruby on Rails
    • Active Record(applicationのObjectをDBのテーブルにmapping), Action Pack(Web frameworkの「Web」の部分を扱う), Active Support(時刻計算やloggingなどの汎用的な問題を扱うutility)
    • gem unpack activerecord -v=4.1.0

Ch09 Active Recordのdesign

  • Object Relational Mapping
    • Relational DB + Object Oriented Programming
  • Active Recordのまとめられ方
    • Active Support + Active Model
    • auto loading by ActiveSupport::Autoload
    • ActiveRecord::Base
      • Moduleの外側にある機能をまとめるClass. Moduleの集まり
    • Moduleのincludeで,instance methodとClass methodを手に入れる
    • history
      • Active RecordからActive Modelが分離
        • Active Record: DB操作, Active Module: Object Modelの操作
  • 学んだこと
    • ActiveRecord::Baseは,究極的なopen Class
    • 疎結合,simplicity, 重複の排除という基本的な設計原則を実現するための技法が,ほかの言語と大きく異なる

Ch10 Active SupportのConcern Module

  • history
    • Rails 2では,VCRの例と同じ形だった: includeとextendのtrick
      • 欠点: 複雑 + 連鎖のために柔軟性down
  • ActiveSupport::Concern
    • Module#append_features
    • Concern#append_features
      • concern(ActiveSupport::ConcernをextendしたModule)のなかで,別のconcernをincludeしない
      • 依存を管理する.最小主義の依存管理system
  • 学んだこと
    • 柔軟性のためのmeta programming
      • codeが複雑になったり,排除しにくい重複が見つかった時に使う.
    • Moduleの相互作用のような基本的な部分でも,meta programmingで変更できる

Ch11 alias_method_chainの盛衰

  • problem
    • around aliasの問題
    • rename, shuffleが繰り返され,実際に呼び出しているmethodがどのversionか追跡がdifficult
    • そもそも,存在自体が不要
      • 別のModuleに定義して,includeすればよい.
      • Classのmethodへの機能追加には,Module#prependを使うのが〇
        • Ruby1.9との互換性の問題がなくなれば,Railsや周辺でもalias_method_chainを使う理由がなくなる
  • 学んだこと
    • 素直なObject指向で解決できれば,meta programmingは使わない

Ch12 attribute methodの進化

  • どのようにmeta programmingを使うか
  • codeが複雑になり,微妙に意味が違ってくる中で起きたことを見る.
  • Rails 1: はじめはsimple
    • method_missing
  • Rails 2: performanceに注目
    • method_missingは遅い
    • → ghost method + dynamic method
      • 各Classで1度だけmethod_missingを呼ぶ
    • backにDBのカラムがない派生fieldなどのattributeへの対応.→ attribute accessorを定義しない
  • Rails 3, 4: もっと特殊なcase
    • Rails 4ではattribute accessorの定義時に,UnboundMethodに変更し,Method cacheに保存している. → ほかのClassが同じ名前のattributeを持ち,同じaccessorが必要な場合はcacheから取り出す.
      • performanceが目に見えて変わる.
      • ReaderMethodCacheの初期化処理にコメントがある
      • attribute methodがどれだけ深くて複雑なものになったか,どれだけの数の特殊ケースに対応しているか,最初はsimpleだったところからどれだけ変わったか,を示している.
      • Rubyのsafeなmethod nameにするための対応
  • 学んだこと
    • 「最初から正しくやる」よりも「進化的設計」に傾いている.
      • Rubyが柔軟性の高い言語であるため
      • 最初から完璧なmeta programmingのcodeを書くことは難しい
    • → codeはできるだけsimpleに保ち,必要になったら複雑にする

Ch13 最後の教訓

  • meta programming: simple, clean, and tested codeを書くための単なるtool

付録

A よくあるidiom

  • mimic method
    • mimicry: 擬態
    • 偽装method
    • Campingの例
      • class Help < R '/help' ← R('/help')と読み替えるとわかる
    • attributeの不具合
      • attributeのsetterは,privateでもselfをつけて呼び出せる
  • nil guard
    • ||=
      • OR と =
    • falseになりうるときは使わない
  • 自己yield
    • blockにObject自身を渡せる
    • Faradayのe.g.
      • 新しく生成したConnection Objectをselfとしてyieldに渡す
    • tapのe.g.
      • 呼び出しのchainを断ち切らずに,中間の作業をtap {} で差し込む
  • Symbol#to_proc
    • one call blockを短い記述に置き換える
    • inject(init)などで使える

B DSL

  • ←→ GSL(General)
  • e.g. UNIXのshell, VBA, make, Ant, ...
  • GPLを,自分の問題に特化したDSLにする.
  • 内部DSLと外部DSL
    • Markabyなどの,大きなGPLの内部にあるDSLは内部DSL.makeなどの独自のparserを持った言語は外部DSL
    • RubyGPLとしての構文の制約が小さい.
      • Rakeは内部DSL ←→ Ant(Java), make(C)は外部DSL

C 魔術書

  • 今までのものの短いsample

D から騒ぎ

  • nilについて
  • Null Object
    • 未初期化の参照をNull Objectにreplace
  • NilClassにmethod_missingを追加.
    • methodの連結にも対応可能
  • →black hole
    • nilを使ったNull Object
    • 未初期化の参照の問題を回避しつつ,method呼び出しを無限のNull Objectの穴に吸い込む
  • bugに気づきづらくなるというdemeritも考慮