2016 メタプログラミングRubyのまとめ

Table of Contents

ホーム / 講義 / 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.2 オープンクラス

クラス定義

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 変数 (オブジェクトが持つ状態)

object_instance_variables.rb

  • インスタンス変数はオブジェクトにあり,
  • メソッドはクラスにある
  • オブジェクトに所属するメソッドは,特異(メソッド)

クラス再び

「(({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

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クラスのサブクラス
  • つまりクラスはモジュール

private.rb

もう一つの学習機会

(({ class M; end })) => "M is not a class"

M という名前の衝突.一つはモジュール名,もう一つはクラス名.

2.4 引かれていない線

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

メソッド呼び出しを深く理解する

メソッドを呼び出すこと

  • メソッドを探す ( ((* メソッド探索 *)) )
  • メソッドを実行 ( ((* self *)) が必要)
    • self は実行の主体

メソッド探索

(現在実行の)オブジェクトのクラスを探しメソッドを見つける

((* レシーバ )) と (( 継承チェーン *))

レシーバ

呼び出すメソッドが属するオブジェクト

継承チェーン

lookup.rb

  • ruby 1.8
    [MySubclass, MyClass, Object, Kernel]
    
  • ruby 1.9~
    [MySubclass, MyClass, Object, Kernel, BasicObject]
    

Kernel はモジュール

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

lookup_modules.rb

  • include は継承に似ていて,
  • self クラスとsuperclass の間に入る
Kernel

kernel.rb

(({ 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.rb

  • self カレントオブジェクト
  • self のコンテキストが実行の場
  • トップレベルコンテキスト main
クラス定義とself

クラスやモジュールの定義中,self は?

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

オブジェクトとは何か?

  • インスタンス変数の集り + クラスへのリンク

クラスとは何か?

  • Classクラスのインスタンス + インスタンスメソッド一覧 + スーパーク ラスへのリンク
  • ClassクラスはModuleクラスのサブクラス
  • つまりクラスはモジュール

private.rb

3 メソッド

3.1 静的言語と動的言語,

静的言語

  • コンパイル時に,間違ったメソッドの使い方を指摘してくれる,かわり に,
  • コンパイル時に持っていないメソッドは使えない.動的な拡張性や柔軟性 を欠く

rubyは動的.

3.2 重複問題

  • コンピュータの各部品とそのコストの一覧を作成するレポート機能
  • 少しだけ違うよく似たコードが山のように存在する

レガシーシステム DS

computer/data_source.rb

def get_#{objects}_#{properties}(id) の集り

ダブル,トリプル,トラブル

よく似たコードの羅列

DS クラスを Computer クラスのオブジェクトでラップする

computer/boring.rb

  • 異なるメソッド中の,よく似たコードの羅列
重複コード排除のための二つの方針
  • 動的メソッド
  • method_missing()を使う

3.3 動的メソッドによる重複コードの排除

メソッドを動的に呼び出す

dynamic_call.rb

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 - メソッドを動的に生成する

computer/dynamic.rb

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 の オーバーライド

more_method_missing.rb

((オーバーライド))は,継承チェーン上に存在するメソッドを, 再定義すること

method_missing をオーバーライドして, 実際には存在しないメソッドを呼び出せる

ghost method

openstruct
require 'ostruct'
icecream = OpenStruct.new
icecream.flavor = "ストロベリー"
icecream.flavor

属性メソッドがゴーストメソッド

my_ostruct.rb

動的プロキシ

  • ゴーストメソッドは,ラッパーでよく使われる
  • メソッド呼び出しを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)
(({respond_to?()}))のオーバーライド
class Computer
  def respond_to?(name)
    @data_source.respond_to?("get_#{method}_info") || super
  end
end

superを呼び出すのは他のメソッドの面倒を見てもらうため

(({Object#methods()}))もオーバーライド?

リファクタリングのまとめ
  • 動的メソッドと動的ディスパッチ 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は,既存のクラスのコンテキストでブロックを評価 する

file:///users/home/masayuki/meta-ruby.git/org/~suzuki/Lects/meta-ruby/code/class_definitions/class_eval.rb

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 に属していると思っている.

file:///users/home/masayuki/meta-ruby.git/org/~suzuki/Lects/meta-ruby/code/class_definitions/class_eval.rb

@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種類しかない。それが通常のオブジェクト化かモ ジュールになる。
  2. モジュールは1種類しかない。それが通常のモジュール、クラス、特 異クラス、プロキシクラスのいずれかになる。
  3. メソッドは1種類しかない。メソッドはモジュール(クラス)に住んでいる。
  4. すべてのオブジェクトは「本物のクラス」を持つ。それは特異クラス か通常のクラスである。
  5. すべてのクラスはスーパークラスを持っている。ただしBasicObject にはスーパークラスはない。
  6. オブジェクトの特異クラスのスーパークラスは、オブジェクトのク ラスである。

    クラスの特異クラスのスーパークラスは、クラスのスーパークラスの特異クラス。

  7. メソッドを呼び出すとき、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 がメソッドに似ている [/]

  1. [ ] 項数に厳しく
  2. [ ] 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()でメソッドに戻せる クラスが異なると,例外が発生
呼び出し可能オブジェクトのまとめ

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

  1. [ ] ブロック
    • オブジェクトではないが,呼び出し可能
    • 定義されたスコープで評価される
  2. [ ] Proc
    • 定義されたスコープで評価される
  3. [ ] lambda
    • Proc クラスのオブジェクト,クロージャ
    • 定義されたスコープで評価される
  4. [ ] メソッド
    • オブジェクトにつながれ,
    • オブジェクトのスコープで評価される

5.6 ドメイン特化言語を書く

イベントの定義

event "注文が殺到" {
  recent_orders = ... # (データベースから読み込む)
  recent_orders > 1000
}
イベント間の共有

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
クイズの答え
redfalg.rb の中身

*events.rb という名前のファイルすべてに対して

ファイルをロード (実行) し, 定義されたイベント組に対し,

新しいオブジェクトをクリーンルーム用に作成し,

定義されたセットアップに対し, クリーンルーム内でセットアップを実行する

クリーンルーム内でイベントを実行し, イベントがあれば,アラートを出す

@setups, @events はグローバル変数のようで良くない
もっと良い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)
コードと変数のスコープ

6.2 オブジェクト指向パラダイムとは

6.3 rubyのやり方

オブジェクトモデル

クラスもオブジェクト

名前の探索

動的な定義

メタプログラマブル

スコープとクロージャで,ブロックもオブジェクト

オブジェクトと特異クラスとクラス階層

Author: suzuki@cis.iwate-u.ac.jp

Created: 2017-02-05 日 11:45

Emacs 25.1.50.2 (Org mode 8.2.10)

Validate