2016 メタプログラミングRubyのまとめ
Table of Contents
- 1. イントロダクション
- 2. オブジェクトモデル
- 3. メソッド
- 4. クラス定義
- 5. ブロック
- 6. メタプログラミング Ruby のまとめ
ホーム / 講義 / ruby / OO / poker開発 / emacs / meta-ruby / note / github-repos / svn-repos 2015 /
1 イントロダクション
1.1 Ruby のメタプログラミングの例
- 外部システムに接続する Rubyプログラムで, あらゆるメソッド呼び出しを受付, それを外部システムに送ることのできるラッパが書ける
- DSLを Ruby を拡張することで実現する
- Java プログラマには想像もできないレベルで,コードの重複の排除が可能
- どんなクラスにも自分の欲しいメソッドが追加でき
- 監視したいメソッドの始めと終わりにロギング機能を追加できる
- 好きなクラスをすきな時に継承できる
1.2 頭文字 M
メタプログラミングとは
コードを記述するコードを記述すること
コードジェネレータとコンパイラ
コードジェネレータとコンパイラは静的メタプログラミング Rubyでは((* 動的メタプログラミングが可能 *))
- 実行時に自分自身を操作するメタプログラミング
ゴーストタウンと市場
ソースコードを活気ある住民であふれた世界と考える そこには ((* 変数 )),(( クラス )),(( メソッド *)) が住む それらを言語要素と呼ぼう 多くのプログラミング言語で,言語要素は,肉体のないゴーストだ ソースコードに居るときは目に見えるが, 実行時には姿が消える.まるでゴーストタウンだ.
- C++では実行時にインスタンスメソッドについて質問することはできない
Rubyでは,実行時もにぎやかな市場だ
メタプログラマ ボブの物語
ボブの最初の試み
introduction/orm.rb introduction/orm.rb オブジェクト関係マッピングを行うコード
メタプログラミングに突入
introduction/orm-meta.rb introduction/orm-meta.rb
頭文字 M セカンドステージ
メタプログラミングとは,言語要素を実行時に操作するコードを記述す ること ActiveRecord::Base を継承するだけで, 実行時にアクセッサメソッドが定義できる
メタプログラミングと Ruby
Ruby は現在,もっともメタプログラミングに適した言語
2 オブジェクトモデル
お断り
記法
もともとは RubyDocument (RD) で書いた文書を Emacs Org-mode にしまし た。RDが優れている記法はそのまま残してあります。
((…)) の箇所です。(({…})) Rubyソース・コードです。
codes
教科書中のコードをダウンロードしたものを,org-mode にし て,~suzuki/lects/meta-ruby/code/object_model_src.org に置きました。 copy して使ってください。
2.1 Object
2.2 オープンクラス
file:///users/home/masayuki/meta-ruby.git/org/~suzuki/COMM/Prog/ruby/meta/object_model/test_alphanumeric.rb
file:///users/home/masayuki/meta-ruby.git/org/~suzuki/COMM/Prog/ruby/meta/object_model/replace.rb
- クラスは定数で,
- クラス定数で指定でき, String, class String … end;
- 変更に対して開かれている.
クラス定義
3.times do class C puts "Hello" end end 'end'
クラス定義の中身
(({class})) はそのクラスのスコープを開き,(({end}))までの間のプログラムを そのスコープ内で,実行していく
- 既存のメソッドを(無意識に)変更することもできる.
- 無意識は問題だが,意識的な場合は便利なこときまわりない.
file:///users/home/masayuki/COMM/Prog/ruby/meta/object_model/open_class.rb
クラスへの (拡張) メソッドをどこにおくか
- (({class String}))と書けば,既存の String クラスが開かれ,変更可 能.
- (({calss MyString < String})) と書けば,独自の String クラスの拡張 が作れる.
- (({def s.method … end})) と書けば,あるオブジェクトだけが拡張で きる
2.3 クラスの真実
オブジェクトの中身
instance 変数 (オブジェクトが持つ状態)
- インスタンス変数はオブジェクトにあり,
- メソッドはクラスにある
- オブジェクトに所属するメソッドは,特異(メソッド)
クラス再び
「(({class C})) 定義されるクラス C もオブジェクト」
- クラスは Class クラスのオブジェクト。 CはClass クラスのオブジェクト
o.class, (o.class).instance_methods, C.superclass, C.ancestors
Classは,(({new()})), (({allocate()})), (({superclass()})) のメソッ ドが追加された Module
定数 object_model/constant.rb
モジュール構造の入れ子構造が定数のスコープ.
module MyModule MyConstant = 1 class MyClass MyConstant = 2 end end
二つの定数のパス
- ::MyModule::MyConstant
- ::MyModule::MyClass::MyConstant
定数をまとめるだけのモジュールのことを,((ネームスペース))と呼ぶ
load と名前空間
mod.rb をロードし,その後実行を続ける場合を考える
- mod.rbには変数やクラスの定義がある.
- load('mod.rb')を実行すると,実行後変数は消えるが,定数は残る.
- load('mod.rb', true) とすると,無名モジュールを作成し, そのスコープで mod.rb を実行する.定数も破棄される
オブジェクトとクラスのまとめ
オブジェクトとは何か?
- インスタンス変数の集り + クラスへのリンク
クラスとは何か?
- Classクラスのインスタンス + インスタンスメソッド一覧 + スーパーク ラスへのリンク
- ClassクラスはModuleクラスのサブクラス
- つまりクラスはモジュール
もう一つの学習機会
(({ class M; end })) => "M is not a class"
M という名前の衝突.一つはモジュール名,もう一つはクラス名.
2.4 引かれていない線
2.5 メソッドを呼び出すときに何が起きているの?
メソッド呼び出しを深く理解する
メソッドを呼び出すこと
- メソッドを探す ( ((* メソッド探索 *)) )
- メソッドを実行 ( ((* self *)) が必要)
- self は実行の主体
メソッド探索
(現在実行の)オブジェクトのクラスを探しメソッドを見つける
((* レシーバ )) と (( 継承チェーン *))
レシーバ
呼び出すメソッドが属するオブジェクト
継承チェーン
- ruby 1.8
[MySubclass, MyClass, Object, Kernel]
- ruby 1.9~
[MySubclass, MyClass, Object, Kernel, BasicObject]
Kernel はモジュール
モジュールとメソッド探索
- include は継承に似ていて,
- self クラスとsuperclass の間に入る
Kernel
(({ print })) は Kernel モジュールのプライベートインスタンスメソッ ド
RubyGems の例
require 'rubygems' gem 'rails', '= 2.3.3'
# gems/rubygems-update-1.3.3/lib/rubygems.rb module Kernel def gem(gem_name, *version_requirements) end end
メソッドの実行
- self カレントオブジェクト
- self のコンテキストが実行の場
- トップレベルコンテキスト main
クラス定義とself
クラスやモジュールの定義中,self は?
2.6 オブジェクトとクラスのまとめ
オブジェクトとは何か?
- インスタンス変数の集り + クラスへのリンク
クラスとは何か?
- Classクラスのインスタンス + インスタンスメソッド一覧 + スーパーク ラスへのリンク
- ClassクラスはModuleクラスのサブクラス
- つまりクラスはモジュール
3 メソッド
3.1 静的言語と動的言語,
静的言語
- コンパイル時に,間違ったメソッドの使い方を指摘してくれる,かわり に,
- コンパイル時に持っていないメソッドは使えない.動的な拡張性や柔軟性 を欠く
rubyは動的.
3.2 重複問題
- コンピュータの各部品とそのコストの一覧を作成するレポート機能
- 少しだけ違うよく似たコードが山のように存在する
レガシーシステム DS
def get_#{objects}_#{properties}(id) の集り
ダブル,トリプル,トラブル
重複コード排除のための二つの方針
- 動的メソッド
- method_missing()を使う
3.3 動的メソッドによる重複コードの排除
メソッドを動的に呼び出す
Object#send() によるメソッド呼び出し
- obj.send(:my_method, 3)
メソッド名に対応するシンボル値と,引数を与えて, obj.my_method(3) と同じ働きをする
動的ディスパッチ という
Camping の例
YAML による属性値の設定 admin : Bill title : Rubyland topic : Ruby and more
ユーザは好きな属性が使える
- conf.admin = 'Bill' … のようなコードをあらかじめ用意できない
Test::Unit の例
名前が test で始まるメソッドをテストメソッドとしている
method_names = public_instance_methods(true) tests = method_names.delete_if { |method_name| method_name !~ ^test.}
このtests配列のメソッドを,後で send を使って呼び出す
メソッドを動的に定義する
Module#define_method() でメソッドをその場で定義する dynamic_definitioon.rb
動的メソッド と呼ぶ
Computerクラスのリファクタリング
もとのコード computer/boring.rb
手順1 - 動的ディスパッチャの追加
computer/send.rb component メソッドが動的ディスパッチャ
手順2 - メソッドを動的に生成する
def self.define_component(name) …
- クラスメソッド define_component の定義
- define_component中 name の値の名前を持つメソッドを生成する
手順3 - コードにイントロスペクションをふりかける
3.4 method_missing()による重複コードの排除
ruby では存在しないメソッドも呼び出せる
method_missing.rb 存在しないメソッド呼び出しとエラー
メソッド探索の仕組み
- レシーバの継承チェーン上のインスタンスメソッドを探す
- なければ,レシーバのmethod_missing()メソッドを呼び出す
- method_missing()はBasicObjectのインスタンスメソッド (ruby 1.9)
method_missing の オーバーライド
((オーバーライド))は,継承チェーン上に存在するメソッドを, 再定義すること
method_missing をオーバーライドして, 実際には存在しないメソッドを呼び出せる
ghost method
openstruct
require 'ostruct' icecream = OpenStruct.new icecream.flavor = "ストロベリー" icecream.flavor
属性メソッドがゴーストメソッド
動的プロキシ
- ゴーストメソッドは,ラッパーでよく使われる
- メソッド呼び出しをmethod_missing()に集中させる ラップしたオブジェクトに投げる
Flicrの例
require 'flickr' flickr = Flickr.new(YOUR_API_KEY) xml = flickr.tags_getListUser('user_id' => '59542755@N00') tags = xml['who']['tags']['tag'] tags.grep /rails/
flickr はAPIが拡張された場合でも対応可能
class Flickr def requrest(method, *params) response = XmlSimple.xml_in(http_get(request_url(method, params)), {'ForceArray' => false}) raise response['err']['msg'] if response['stat'] != 'ok' response end def method_missing(method_id, *params) request(method_id2name.gsub(/_/, '.'), params[0]) end ...
Flickr#method_missing() は名前を変更して,Flickr#request()に委譲
動的プロキシ
- オブジェクトがゴーストメソッドを受け取り,
- 何らかの処理をして,
- 他のオブジェクトに転送する
委譲 (コラム)
DelegateClass() は,ミミックメソッド
- 未定義のメソッド呼び出しを,
- 与えられたクラス(のオブジェクト)に委譲する
- クラスを返す
frank = Assistant.new("Frank") anne = Manager.new(frank) anne.attend_meeting anne.read_email anne.check_schedule
anne は理解できないメッセージをすべて frank に転送している
Computerクラスのリファクタリング
元のコードfile://~suzuki/COMM/Lects/meta-ruby/code/methods/computer/boring.rb
Computerクラスは動的プロキシになる
リファクタリングするぜ
file://~suzuki/COMM/Lects/meta-ruby/code/methods/computer/proxy.rb
my_computer = Computer.new(42, DS.new) my_computer.cpu
resopond_to?()のオーバーライド
(({mouse()}))) は本物のメソッドではない.
- ドキュメントには現れない
- (({Object#methods}))にも登場しない
- (({Computer}))クラスにゴーストメソッドはあるかと聞いても嘘を つく
cmp = Computer.new(0, DS.new) cmp.respond_to?(:mouse)
class Computer def respond_to?(name) @data_source.respond_to?("get_#{method}_info") || super end end
superを呼び出すのは他のメソッドの面倒を見てもらうため
?
リファクタリングのまとめ
- 動的メソッドと動的ディスパッチ DSのラッパーとして,イントロスペクションを使う
- レガシーシステムに委譲
const_missing() (コラム)
Module#const_missing() 存在しない定数を参照したとき呼ばれる 任意のネームスペースに定義できる
def Object.const_missing(name) name.to_s.downcase.gsub(/_/, ' ') end
3.5 クイズ: バグ退治
method_missing のバグは潰しにくい
file://~suzuki/COMM/Lects/meta-ruby/code/methods/bug_hunt.rb
ブロック局所変数 number のスコープ
file://~suzuki/COMM/Lects/meta-ruby/code/methods/bug_hunt_solution.rb [bug_hunt_solution.rb]
3.6 もっと method_missing()
メソッド名が衝突したら
my_computer = Computer.new(42, DS.new) my_computer.display # -> nil
Computer#display()が nil を返すわけ
Object.instance_methods.grep /^d/
Object#displayがみつかるため,method_missing にならない
動的プロキシでも同じ問題が起こる
ゴーストメソッド名と継承メソッド名の衝突が原因
継承メソッドを消す
- (({Module#undef_method()})) は全てのメソッドを消す
- (({Module#remove_method()})) レシーバのメソッドのみ削除
パフォーマンスの不安
ゴーストメソッドは通常のメソッドより(2倍)遅い
file://~suzuki/COMM/Lects/meta-ruby/code/methods/methods_benchmark.rb
Builderの例
BuilderはXML生成ライブラリ
file://~suzuki/COMM/Lects/meta-ruby/code/methods/builder_example.rb
class BlankSlate def self.hide(name) if instance_methos.include?(name.to_s) and name !~ /^(__|instance_eval)/ @hidden_methods !!= {} @hidden_methods[name.to_sym] = instance_metho(name) undef_method name end end end
予約済メソッド
Objectのメソッドには Ruby が内部的に使うものがある.再定義すると おかしくなる. send(), __id__()
コンピュータクラスの修正
BasicObject
Ruby1.9から ブランクスレート が言語に組み込まれた
irb19で
p BasicObject.instance_methods
まとめ
Computerクラスの重複をなくすリファクタリング 動的メソッド 動的ディスパッチ 動的プロキシ ブランクスレート
file://~suzuki/COMM/Lects/meta-ruby/code/methods/computer/more_dynamic.rb
file://~suzuki/COMM/Lects/meta-ruby/code/methods/computer/final.rb
4 クラス定義
((:class:)) キーワードは,クラスオブジェクト(のコンテキスト)で ((:end:)) までのブロックを実行すること
((:特異クラス:)) はオブジェクトや(オブジェクトとしての)クラスを拡張する
class の中で特異メソッド(クラスメソッド) が使え、クラス定義中に下記 のことが実現できる:
- クラスマクロ 動的なメソッドの生成など
- アラウンドエイリアス メソッド名の付け替えとラップなど
4.1 クラスに関する説明のつかない事
例えば,File クラス
- File はクラスそれともオブジェクト?
- File.open("hoge")は誰がどこで実行する?
- File.new と インスタンスメソッドの initialize の関係は?
4.2 クラス定義のわかりやすい説明
クラス定義の中身
class 式は,クラス定義コンテキストで式の中身を実行する
class MyClass puts 'Hello!' end
class式は定義/変更したクラスを返す
result = class MyClass self end result # => MyClass
クラス定義コンテキストでは self は定義中のクラス自身
カレントクラス
Rubyプログラムのコンテキスト(?)
- カレントオブジェクト self,
- カレントクラス (あるいはモジュール)
カレントクラスを参照する方法
カレントクラスを変更する方法
class MyClass def my_method end end
class式は,クラス名がわからないと無力
カレントクラスと特別な場合
class_eval()
Module#class_evalは,既存のクラスのコンテキストでブロックを評価 する
def add_method_to(a_class) a_class.class_eval do def m; 'Hello!'; end end end
add_method_to String "abc".m # => 'Hello!'
コンテキストの変化
- class_eval は self とカレントクラスの値が変る
- instance_eval は self の値だけが変る
カレントクラスのまとめ
- クラス定義の中では,カレントオブジェクト self は,定義されたク ラスである.
- Rubyインタプリタは,常に,カレントクラスの参照を追跡している def で定義された全てのメソッドは,カレントクラスのインスタンス メソッドになる
- クラス定義のなかでは,カレントクラスは self と同じ
- クラスへの参照をもっていれば, class_eval でオープンできる
クラスインスタンス変数
Rubyインタプリタは,全てのインスタンス変数はカレントオブジェクト self に属していると思っている.
@my_var はMyClassオブジェクトに属している
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.write obj.read # => 2 MyClass.read # => 1 # self.read
クラス変数
class C; @@v = 1; end class D < C; def my_method; @@v; end; end D.new.my_method # => 1
@@v は継承されている!
@@v = 1 class MyClass; @@v = 2; end @@v # => 2
これはrubyの酷いくせ クラス変数は,クラス階層に属している 上の例では,@@v は Object クラスに属している
Bookwormの作業再び
Loan#to_s()のユニットテストの問題点
期待すべき結果が日付や時刻に依存するため,テストが書けない
解決方法
本来の日付や時刻を作成するクラスを置き換えて、 テスト用の日付や時刻を切り替えて使えるようにする.
クラスインスタンス変数 @time_class の有無により, Time クラスを使うか, FakeTime クラスを使うか切り替える.
切り替えは、instance_eval でクラスインスタンス変数を切り替えるこ とで行う.
4.3 クイズ: クラスのタブー
(({class})) を使わずに 下記 MyClass
class MyClass < Array def my_method 'Hello!' end end
を定義するruby プログラムを書くこと
クイズの答え
c = Class.new(Array) do def my_method 'Hello' end end MyClass = c
このクラスには最初定数名がない! 無名クラスだ.
Ruby 処理系は、MyClass へ 無名クラスを代入するときに、特別なこと をする。無名クラスの名前付け,すなわちクラス値から定数名への参照 を持つこと,をおこなう。
4.4 特異メソッド
file:///users/home/masayuki/COMM/Lects/meta-ruby/code/class_definitions/paragraph.rb
Paragraph クラスは String クラスのごく僅かな拡張で, その拡張 (titleメソッド)が使われる場所もごく限られている
特異メソッドの導入
特異メソッドの導入 :: 特定のオブジェクトに定義されたメソッド
file:///users/home/masayuki/COMM/Lects/meta-ruby/code/class_definitions/singleton_methods.rb
クラスメソッドの真実
「クラスもオブジェクト」だった.
実は,クラスに対するメソッドの呼び出しは, オブジェクトに対するメソッドの呼び出しと,同じ仕組みだった.
それはクラス・オブジェクトの特異メソッドだ
class MyClass def self.s_method end end
s_method は,オブジェクト MyClass だけに定義されたメソッド.
クラスマクロ
attr_reader, attr_writer, attr_accessor などは, キーワードのように見えるが, 単なるクラスメソッド である
クラスメソッドは,クラス定義中に, クラスコンテキストで実行できる
4.5 特異クラス
オブジェクトモデルの完結
(s) クラスメソッドや特異メソッドが,単なるメソッドにすぎなくなるためのモデル
特異メソッドの謎
def obj.my_singleton_method; 'where is this' end obj.my_singleton_method => 'where is this'
obj だけに存在する my_singleton_method はどこにある?
=> obj – (my_singleton_method) – class ( … )
特異クラスの出現
特異クラス はオブジェクトの特異メソッドが住む場所
「特異クラスはオブジェクトではない」は間違い (s)
特異クラスを定義する特別な構文
class << an_object; end
特異クラスへの参照を得て特異クラスのクラスを見る
obj = Object.new eigenclass = class << obj self end eigenclass.class # => Class
特異クラスに特異メソッドが住んでいる
def obj.my_singleton_method; end eigenclass.instance_methods.grep(/my_/)
メソッド探索再び
オブジェクトモデルを調べる実践的な例
class C def a_method 'C#a_method()' end end
class D < C; end
obj = D.new obj.a_method
class << obj def a_singleton_method 'obj#a_singleton_method()' end end
特異クラスのスーパークラスは?
eigenclass.superclass # => D
#obj.class == obj.class
特異クラスの特異クラス
class << "abc" class << self self end end
特異クラスはクラスでありオブジェクトでもある。 特異クラスの特異クラスも同様。
特異クラスと継承
オブジェクトの特異クラスを返すヘルパーメソッド (({eigenclass}))
class Object def eigenclass class << self; self; end end end
class C def a_method 'C#a_method()' end end
class D < C; end
class C class << self def a_class_method 'C.a_class_method()' end end end
C.eigenclass # => #<Class:C> D.eigenclass # => #<Class:D> D.eigenclass.superclass # => #<Class:C> C.eigenclass.superclass # => #<Class:Object>
大統一理論
Rubyのオブジェクトモデルの7つのルール
- オブジェクトは1種類しかない。それが通常のオブジェクト化かモ ジュールになる。
- モジュールは1種類しかない。それが通常のモジュール、クラス、特 異クラス、プロキシクラスのいずれかになる。
- メソッドは1種類しかない。メソッドはモジュール(クラス)に住んでいる。
- すべてのオブジェクトは「本物のクラス」を持つ。それは特異クラス か通常のクラスである。
- すべてのクラスはスーパークラスを持っている。ただしBasicObject にはスーパークラスはない。
- オブジェクトの特異クラスのスーパークラスは、オブジェクトのク
ラスである。
クラスの特異クラスのスーパークラスは、クラスのスーパークラスの特異クラス。
- メソッドを呼び出すとき、Ruby はレシーバの本物のクラスに向かっ て、「右へ」進み、継承チェーンを「上へ」進む。
4.6 クイズ: モジュールの不具合
モジュールを include すると、モジュールのインスタンスメソッドは手に 入るが、モジュールのクラスメソッドは手に入らない。
module MyModule def my_method; 'hello'; end end
class MyClass class << self include MyModule end end
クラスメソッドの住処である特異クラスで include すればよい。
クラスの特異クラスにメソッドを定義することを クラス拡張 と呼ぶ
オブジェクトの特異クラスにメソッドを定義することを オブジェクト拡張 と呼ぶ
Object#extend
obj.extend MyModule
class MyClass extend MyModule end
4.7 エイリアス
省略
4.8 クイズ: 壊れた計算
1+1 # => 3
となるように Fixnum クラスの + メソッドを書き換える
class Fixnum alias :old_plus, :+ def +(value) self.old_plus(value).old_plus(1) end end
5 ブロック
この章で理解すべきこと
- スコープ
- クロージャ
- クロージャによるスコープの操作
- 呼び出し可能オブジェクトへの変換
5.1 ブロックの基本
class Myclass def mymethod(a, b) z = -100 if block_given? yield(a, b) else a + b end end end z=100 p @obj = Myclass.new p @obj.mymethod(1,2) { |x,y| x+y+z } p @obj.mymethod(1,2) do |x,y| x+y+z; end p @prc = Proc.new { |x,y| x+y+z } p @prc.binding p (@prc.binding).eval("z")
ruby babel/mymethod.rb
ブロックの作成
- do … end が block
- メソッド呼び出しの時のみ
- (s) class, def でも作れる
ブロックが与えられているか?
- block_given? で調べられる
ブロックの呼び出し
n- 呼ばれたメソッド側で yield により呼び出せる
- しかし,block は,block が作られた 環境 で実行される
5.2 クイズ: Ruby#
using
リソースの確保,コードの実行,リソースの開放を記述。コードの実行中に例 外が発生しても,確実にリソースを開放する。
using の実装
module Kernel def using(resource) begin yield ensure resource.disclose end end end
5.3 クロージャ
変数のスコープを超える方法を学ぼう
コードの実行
- ブロックはコード
- *self*が実行の主体 (場)
- クロージャ = コード + 束縛 (局所変数,インスタンス変数,… )
file://~suzuki/COMM/Lects/meta-ruby/code/blocks/closure.rb
def do_block x = "do block" yield("yield_block") end class Myclass C = "constant" def make_and_do_block x = "make block" do_block {|y| "Variables C:#{C} x:#{x}, y:#{y}" } end end puts block = (Myclass.new).make_and_do_block
ruby babel/make_and_do_block.rb
Variables C:constant x:make block, y:yield_block
Myclass
のオブジェクトのmake_and_do_block
メソッド中で作られたブ
ロック {|y| "Variables C:#{C} x:#{x}, y:#{y}" }
は,do_blockの中で,
yield
で呼び出され実行される。
- ="Variables C:#{C} x:#{x}, y:#{y}"= が実行され,
- 実行されている環境で見える, 定数
C
,x=, =y
の値が使われる。 - その値から,=Myclass=の中の
make_and_do_block
メソッドの中にいる ことがわかる。
ブロックが生まれるとき,自身が生まれた環境を閉じ込めた ((クロージャ)) となる
クロージャが実行される時は,その環境で実行される
- 定数はselfのクラスから辿れる
- インスタンス変数、特異メソッドには self から辿れる
ブロックローカル変数
def my_method yield end top_level_variable = 1 my_method do top_level_variable += 1 local_to_block = 1 end puts 'top_level_variable = ',top_level_variable puts 'local_to_block =', local_to_block
ruby babel/block_local_variables.rb
top_level_variable は block のネスティング が行われ, 外側のブロックのローカル変数を参照していることを示している.
local_to_block は,block の中で生まれたが, block の実行終了とともに消滅した.
スコープ
- 束縛
- self インスタンス変数,メソッド(in self.class)
- 定数の木
- グローバル変数
スコープの変更
束縛を Kernel#local_variables() メソッドで追跡
file://~suzuki/COMM/Lects/meta-ruby/code/blocks/scopes.rb
- トップレベル スコープ
- MyClass 定義のトップレベル スコープ
- メソッドの中のスコープ メソッドのローカル変数,インスタンス変数,定数
((スコープゲート))
プログラムが新しいスコープを開く箇所
- クラス定義 (({class}))
- モジュール定義 (({module}))
- メソッド呼び出し (({def}))
v1 = 1 class MyClass # クラスの入り口 v2 = 2 local_variables # => ["v2"] def my_method # メソッドの入り口 v3 = 3 local_variables end # メソッドの出口 local_variables # => ["v2"] end # クラスの出口 obj = MyClass.new obj.my_method # => ["v3"] obj.my_method # => ["v3"] local_variables # => ["v1", "obj"]
- class や module のブロックは定義時に実行
- def のブロックはメソッド呼び出し時に実行
スコープのフラット化
クラスゲートを越える
- 方針
- class と同じ効果のあるメソッドに,my_var を閉じ込めたクロー ジャを渡す
- code
- file://~suzuki/COMM/Lects/meta-ruby/code/blocks/flat_scope2.rb
メソッドゲートを越える
- 方針
- define_method に,my_var を閉じ込めたクロージャを渡す
- code
- file://~suzuki/COMM/Lects/meta-ruby/code/blocks/flat_scope3.rb
スコープの共有化
://~suzuki/COMM/Lects/meta-ruby/code/blocks/shared_scope.rb]]
- define_methodsの実行
- ブロック内で shared が定義され,
- shared への参照と代入をもったクロージャを使って, Kernel モジュール内に couter, inc メソッドを定義する
- 二つのメソッドからだけ参照できる安全な変数の生成
スコープのまとめ
- Rubyのスコープには束縛がある
- スコープは class, module, def のスコープゲートで区切られ。
- スコープゲートは,Class.new(), Module.new(), Module#define_method() で置き換え,それらに束縛を閉じこめたクロージャを与える。
- クロージャにより,束縛の共有も可能となる
(s) この辺りは,SICP の lambda による実現の方が,シンプルでわかりや すい。
5.4 instance_eval()
コードと束縛を好きなように組み合わせるもう一つの方法
- obj.instance_eval block
- オブジェクトobjのコンテキストで,
- ブロックblockを評価する
://~suzuki/COMM/Lects/meta-ruby/code/blocks/instance_eval.rb
v = 2 obj.instance_eval { @v = v } obj.instance_eval { @v }
生成された環境でのローカル変数にも,
objのインスタンス変数にもアクセスできる
objをselfにして, クロージャを実行するということ
instance_exec (ruby 1.9)
class C def initialize @x, @y = 1, 2 end end C.new.instance_exec(3) {|arg| (@x+@y) * arg }
カプセル化の破壊
instance_eval を使うとカプセル化が破壊できる
カプセル化の破壊が正当化されることもある
RSpecの例
file://~suzuki/COMM/Lects/meta-ruby/code/blocks/rspec.rb
@object = Object.new @object.instance_eval { @options = Object.new } @object.should_receive(:blah) @object.blah
クリーンルーム
- クリーンルーム
- ブロックを評価するためだけに作られたオブジェク トのこと
file://~suzuki/COMM/Lects/meta-ruby/code/blocks/clean_room.rb
5.5 呼び出し可能オブジェクト
ブロックの使用
- コードの保管
- ブロックをyieldを使った呼び出し
コードを保管できる状況
- (({Proc})) の中.ブロックがオブジェクトになる
- (({lambda})) の中.
- メソッドの中
Procオブジェクト
ブロックはオブジェクトではないが, Proc はブロックをオブジェクトにでき, 後から呼び出せる (((遅延評価)))
inc = Proc.new { |x| x+1 } inc.call(2) #=> 3 'end'
カーネルメソッド (({lambda})), (({proc})) も ブロックを(({Proc}))に変換できる.
dec = lambda { |x| x-1 } dec.class # => Proc dec.call(2) # => 1
&修飾
- 他のメソッドをブロックに渡す
- ブロックをProcに変換する
file://~suzuki/COMM/Lects/meta-ruby/code/blocks/ampersand.rb
- ブロックを 引数 &operation で受ける
- &operationを渡すとブロックを渡すことになる
def my_method(&the_proc) the_proc end p = my_method {|name| "Hello, #{name}"} puts p.class puts p.call("Bill")
=>
Proc Hello, Bill
&the_proc は,ブロックを(({Proc}))に変換して受ける 次の the_proc は,(({Proc})) 値を返す
(({Proc})) をブロックへ戻すには
file://~suzuki/COMM/Lects/meta-ruby/code/blocks/proc_to_block.rb
HighLineの例
file://~suzuki/COMM/Lects/meta-ruby/code/blocks/highline_example.rb
name = hl.ask("Name?", lambda {|s| s.capitalize}) puts "Hello, #{name}"
Proc 対 lambda
ブロックを Proc にする方法
- Proc.new()
- lambda { }
- &修飾
- …
Proc と lambda でできるオブジェクトは少し違う
- Proc は Proc, lambda は lambda
https://d.hatena.ne.jp/vividcode/20100813/1281709854]] が詳しい
https://doc.okkez.net/static/193/doc/spec=2flambda_proc.html
Proc, lambda, return
file://~suzuki/COMM/Lects/meta-ruby/code/blocks/proc_vs_lambda.rb
def double(callable_object) callable_object.call * 2 end l = lambda { return 10 } double(l) # => 20
lambda はメソッド
def another_double p = Proc.new { return 10 } result = p.call return result * 2 end another_double # => 10
https://doc.okkez.net/static/193/doc/spec=2flambda_proc.html
Proc のリターンは,Proc の定義された環境から return (直前の環境へ戻る)
Proc, lambda, arity
引数の確認方法の違い
- lambda は厳格 (メソッドに準拠)
- Proc は柔軟
p = Proc.new { |a,b| [a, b]} p.arity # => 2
p.call(1, 2, 3) # => [1, 2] p.call(1) # => [1, nil]
Proc対lambda: 判定
lambda がメソッドに似ている [/]
[ ]
項数に厳しく[ ]
return で自身を終える
Proc はコンテキスト中のコードの一部, lambda は独立したコード
Kernel#proc
メソッド再び
file:///users/home/masayuki/COMM/Lects/meta-ruby/code/blocks/methods.rb]]
- Object#method() でメソッドを,Method オブジェクトとして取得可
- Method オブジェクトは,Method#call() で呼び出し可能
- Method オブジェクトは,属するオブジェクトのスコープで実行される
- Method#unbind() は属するオブジェクトを引き離し,UnboundMethod オブジェクトが返る
- UnboundMethodはMethod#bind()でメソッドに戻せる クラスが異なると,例外が発生
呼び出し可能オブジェクトのまとめ
呼び出し可能オブジェクト [/]
[ ]
ブロック- オブジェクトではないが,呼び出し可能
- 定義されたスコープで評価される
[ ]
Proc- 定義されたスコープで評価される
[ ]
lambda- Proc クラスのオブジェクト,クロージャ
- 定義されたスコープで評価される
[ ]
メソッド- オブジェクトにつながれ,
- オブジェクトのスコープで評価される
5.6 ドメイン特化言語を書く
イベントの定義
event "注文が殺到" { recent_orders = ... # (データベースから読み込む) recent_orders > 1000 }
初めてのDSL
イベント間の共有
file:///users/home/masayuki/COMM/Lects/meta-ruby/code/blocks/monitor_blocks/more_test_events.rb
setup で共有変数の初期化をし, event で共有変数を参照する
クイズ: より良い DSL
setup 命令の追加
ビルの逃亡
def event(name, &block) @events[name] = block end
クイズの答え
*events.rb という名前のファイルすべてに対して
ファイルをロード (実行) し, 定義されたイベント組に対し,
新しいオブジェクトをクリーンルーム用に作成し,
定義されたセットアップに対し, クリーンルーム内でセットアップを実行する
クリーンルーム内でイベントを実行し, イベントがあれば,アラートを出す
もっと良いDSL
共有スコープをつかってグローバル変数を取り除く
file:///users/home/masayuki/COMM/Lects/meta-ruby/code/blocks/monitor_final/redflag.rb
lambda を使い, 共有スコープのために event, setup, each_event, each_setup メソッドを動的に定義 する
*events.rb という名前のファイルすべてに対して ファイルをロード (実行) し, 定義されたイベント組に対し,
新しいオブジェクトをクリーンルーム用に作成し,
定義されたセットアップに対し, クリーンルーム内でセットアップを実行する
クリーンルーム内でイベントを実行し, イベントがあれば,アラートを出す
(s)
- load するファイルごとに,eventsとsetups を nil に初期化する必 要あり?
5.7 参考
rhg source eval.c#Init_Proc
6 メタプログラミング Ruby のまとめ
6.1 計算のモデルとプログラミング言語
- 値
- 計算への入力であり,出力
- コード
- 入力 => op => 出力
- 変数
- 状態
- (no term)
- コードと変数のスコープ