メタプログラミングRuby
LLPlanets
2011-8-20
⾓征典
kdmsnr@gmail.com

1/50

⾓征典-kdmsnr

2/50

MF'sbliki

3/50

Rubyにおけるメタプログラミングとは?

4/50

内部DSLを書くことである
5/50

プログラミング⾔語Ruby
8章リフレクションとメタプログラミング

豊富なリフレクションAPIを使い、メタプログラミングを実現する。
それは、DSLを書くという発想と結び付いている。

6/50

DSLの種類
byMartinFowler

外部DSL（⾔語外DSL）
内部DSL（⾔語内DSL）
⾔語ワークベンチ→ググって7/50

外部DSLの例
CucumberのGherkin記法
フィーチャ: プロジェクトの閲覧ユーザとして、プロジェクトを閲覧したい。
それは、チケットを割り当てるためだ。
シナリオ: すべてのプロジェクトを⼀覧表⽰する
前提 "TextMateプロジェクト"があるかつ "ホーム"ページを表⽰している
もし "TextMate"リンクをクリックする
ならば "TextMateプロジェクト"ページを表⽰する
8/50

内部DSLの例
tDiaryのテストコード
feature '⽇記の追記' doscenario '⽇付を指定して新しく⽇記を書く' doappend_default_diary('2001-04-23')
visit '/'
click_link "2001年04⽉23⽇"
within('div.day div.section') {page.should have_content"とりあえず⾃前の環境では" +"ちゃんと動いているが、" +"きっと⽳がいっぱいあるに違いない:-P"}

9/50

DSLの再来
DSLに対する関⼼は急激に⾼まってきています。これはRubyコミュニティのおかげと⾔ってもよいでしょう。
―『プログラミングScala』

10/50

おそらくRailsが発端
設定よりも規約（CoC）# usersテーブルとマッピング
class User < ActiveRecord::Baseend

11/50

DSLの⽬的
forプログラマ
⽣産性・理解度・表現⼒の向上

forプログラマ+ドメイン専⾨家
コミュニケーション の円滑化

12/50

DSLにあると嬉しいbyGlennVanderburg

⽂脈が記述できる⽂章のように記述できる単位が記述できる
使える語彙が豊富である
階層データが記述できる
13/50

新しい語彙をうまく導⼊
語彙の増加→新しい視点
サピア＝ウォーフの仮説例）BDD（振る舞い駆動開発）「名前重要」⽂化

14/50

新しい⽂法は導⼊しない
⻑年拒否している機能 があります。
それはマクロ，特にLispスタイルのマクロです。
―『まつもとゆきひろコードの世界』

15/50

メタプログラミング技法の分類・概要・応⽤例

16/50

RubyでDSLを作る⽅針
⾔葉を⾒つける
⽂章のように書いていく
Rubyの⽂法に合わせる

17/50

コレクションがあってシャッフルしたいと思ったら、shuﬄeと書いて、それで動かなきゃいけないんですよ。
―yugui
http://www.atmarkit.co.jp/news/200907/24/
ruby2.html

18/50

Rubyの基本的な道具
オブジェクトと変数
メソッドとブロック
制御構⽂（if,unless,while,...）19/50

メソッドチェインa.k.a.流れるようなインタフェース
3.weeks.ago.saturday?
(0..9).map{|i| i + 1 }.reduce(:+)
User.where(:hobby => "Ruby").
order(:nickname).limit(10).offset(20)20/50カッコの省略# メソッド呼び出し、引数のハッシュリテラル
establish_connection :adapter => "sqlite",:database => "dbfile"# ブロック(do ... end)group :test do gem "shoulda" end# if ⽂
if true then "＼(^o^)／" else '／(^o^)＼' end

21/50

オープンクラス
3.hours.from_now # 今から3時間後を求めたい# あらかじめ以下を定義
class Fixnum
def hours
self * 3600
end
def from_now
Time.now + self
end
end
22/50

クラスマクロ
単なるクラスメソッドの呼び出し
class Book < ActiveRecord::Base
has_many:releases
belongs_to :publisher
validates_presence_of :title, :author
end

23/50

⼤クラス主義とMix-in
module ActiveRecordclass Base# ...
Base.class_eval doinclude ActiveRecord::Persistence
extend ActiveModel::Naming
extend QueryCache::ClassMethods
extend ActiveSupport::Benchmarkableextend ActiveSupport::DescendantsTracker
include ActiveModel::Conversion
include Validations

24/50

クラスもオブジェクト# オブジェクトIDを持っている
Fixnum.object_id # => 2156200840# 任意のモジュールで機能拡張できる
target.extend ActiveModel::Naming# 変数に代⼊できる
fxn = Fixnum
fxn.name# => "Fixnum"

25/50

戻り値がクラスのメソッド
a.k.a.ミミックメソッド# Camping by _whyclass Index < R "/"def getrender :index
end
# ...

# <= Here26/50演算⼦もメソッド

require 'open-uri'require 'hpricot'
doc = Hpricot open "http://www.ruby-lang.org/ja# 「/」で要素を検索している
doc / :div.post / :h3 # => 実⾏結果

27/50

まとまったタスクの記述
Chefのレシピ
directory "/tmp/monkey" doowner "root"group "root"mode 0755
action :create
end

28/50

ブロック付メソッド呼出し
recipe = Recipe.new(:TKG)
recipe.step "⽣卵に醤油を⼊れよくかき混ぜる。"recipe.step "箸で茶碗の飯に適当な窪みを作る。"recipe.step "卵を窪みに流し込み、飯と混ぜる。"# 上のコードを意味的にまとめるとRecipe.create(:TKG) dostep "⽣卵に醤油を⼊れよくかき混ぜる。"step "箸で茶碗の飯に適当な窪みを作る。"step "卵を窪みに流し込み、飯と混ぜる。"
end

29/50

ブロックの「⽂脈」で評価
instance_eval
class Recipe
def self.create &blockobj = self.new
obj.instance_eval &block
obj
end
# ...

30/50

eval族
instance_eval,class_eval
instance_exec,class_exec
eval

31/50

method_missing
a.k.a.ゴーストメソッド
require 'ostruct'ost = OpenStruct.new
ost.iofjklffhdlsakf = 'めちゃくちゃなメソッド'# 呼び出しost.iofjklffhdlsakf # => 実⾏結果

32/50

もっとメタプログラミング

33/50

ennnnnd
http://redmine.ruby-lang.org/issues/5054

module MyModuleclass MyClass
def my_method
10.times do
if rand < 0.5
p :smallennnnnd # <= Here

34/50

各種ライブラリ
ParseTree（1.8only）
Rubinius（処理系）,RubyParser
Ripper（1.9標準添付）
Racc（パーザジェネレータ）
rparsec（パーザコンビネータ）
35/50

Webフレームワークのメタプログラミング

36/50

http://lokka.org/37/50

HTTPメソッドとメソッド
get '/admin/posts' do
# ...
end
put '/admin/posts/:id' do |id|
# ...
end
delete '/admin/posts/:id' do |id|
# ...
end

38/50

HTTPヘッダとメソッド
get 'index.atom'# ...
content_type 'application/atom+xml',:charset => 'utf-8'
# ...
end

39/50

Rubyのバージョンの調整
オープンクラス
unless String.public_method_defined?\(:encoding)class String
def encoding(encoding)
self
end
end
end

40/50

haml
HTML作成⽤の外部DSL- if locale == settings.default_locale- page = Page('home') || Page.new- else- page = Page("home-#{locale}").section.header%h2= page.title.body= page.body

41/50RSpec
テスト⽤の内部DSL
describe "App" docontext "Access pages" doit "should show index" doget '/'last_response.body.should \match('Test Site')
end
end
end
42/50

短縮メソッドの追加
特異メソッドの利⽤
def Page(id)Page.get_by_fuzzy_slug(id.to_s)
end
Page("home")

43/50

プラグインの追加
フックメソッドの利⽤
module Hoptoad
def self.registered(app)app.use HoptoadNotifier::Rack
# ...
end

44/50

eval
@entry = Entry(id)type = @entry.class.name.downcase.to_sym
eval "@#{type} = @entry"

45/50

肯定派？
否定派？

46/50

何も違わん。違うと思うのはお主の⼼の中だけじゃ。
―マスター・ヨーダ

47/50

Rubyのコードは信頼できないって？それはコードじゃなくて、コーダーを信頼してないからだろ？
―DaveThomas

48/50

敢えて⾔うならDSL作るの難しい！
英語っぽく書けても⽇本⼈はあまり嬉しくない
DSLを作るならもっと厳密なチェックやエラーメッセージが欲しいかも49/50

Paolo Perrotta(著), ⾓ 征典 (翻訳)http://www.amazon.co.jp/dp/4048687158
50/50