メタプログラミング実践 - shinjuku.rb #18
DESCRIPTION
shinjuku.rb #18で発表しましたTRANSCRIPT
メタプログラミング実践
@shokai
shinjuku.rb #182013/06/26
•@shokai (しょうかい)
•初参加•趣味:料理、glitch、プログラミング
•Sinatra派
•minitest派
せっかく新宿に来たのだから聖地巡礼しよう!!
→ 小屋が見つからない靴が濡れて足痛い
引きこもってコード書いてたらネタが溜まってきたので放出していきたい
メタプログラミングとは
「コードを記述するコード を記述すること」(p.11)
なるほどわからん
実際やってみたら
わかった!やってみるの大事
今日はメタプログラミングを使って実装してみた3つのGem
をご紹介いたします
% gem install skypeSkype Desktop APIのラッパー
Mac/Linux対応https://github.com/shokai/skype-ruby
method_missingを使用
require 'rubygems'require 'skype'
# チャットSkype.message("shokaishokai", "電話かけます")
# 電話Skype.call("shokaishokai")
call関数やmessage関数はgem内に実装されていないしかしなぜか呼び出せる
module Skype def self.method_missing(name, *args) self.exec "#{name.upcase} #{args.join(' ')}" endend
method_missing
Skype Desktop API
"CALL shokaishokai""MESSAGE shokaishokai こんにちは"
Query文字列をSkype.appに送るだけの簡単仕様
実装されていない関数の呼び出しを受け取る関数
Skype.execの中身はMac/Linux別々に実装
• method_missingはAPIのラッパーを作るのに便利
• (APIに規則性がある場合のみ)
• API側が変わっても、gemを修正する必要がない
• 例:”CALL shokaishokai” が “CALLTO shokaishokai”
に仕様変更されても、Skype.callto “shokaishokai” で呼び出せる
• Twitter gem等でも使われている
% gem install babascriptコンピュータが得意な事はコンピュータが、
人間が得意な事は馬場くん がやってくれる言語
https://github.com/masuilab/babascript
instance_eval, method_missingを使用
@takumibaba
% baba -e 'アイス買ってきて("#{rand 5}本")'
baba -e ’コード’
もしくはbaba ファイル名
結果
#!/usr/bin/env babaif 0 < Time.now.hour and Time.now.hour < 5 もう寝ろ!!else 意識を高めてコードを書こう!end
wake-up.bb
% baba wake-up.bb
実行
ただのRuby…ではなく日本語で書いた部分を馬場君が実行してくれる
class Foo def initialize @name = "bar" endend
foo = Foo.newfoo.instance_eval do puts @name # => "bar"end
instance_eval
babaコマンドの中身 = instance_evalFile.open(fname) do |f| BabaScript::Baba.instance_eval f.readend
コードやブロックをそのインスタンスのコンテキストで実行する
fooのコンテキストで実行されるのでアクセサが無いプロパティ@nameも読める
module BabaScript class Baba
def self.method_missing(name, *args) ## (略) Androidにnameとargsを送信する end
endend
Rubyの関数名には日本語が使える→ method_missingで全部取れる
File.open(fname) do |f| BabaScript::Baba.instance_eval f.readend
• instance_eval + method_missingで言語が歪む
• エラーメッセージがわかりにくくなるので、何でもかんでもinstance_evalするとコード追えなくなる
• rspecなど、英語っぽいDSLで書けるgemで使われているはず
人間を関数のように扱えるようになるスマホアプリ
+人間に命令を送る構文を追加したプログラム言語
→ 人間とプログラム言語の新しい関係
% gem install event_emitter全てのクラス/インスタンスにイベント機能を追加できる
https://github.com/shokai/event_emitter
mix-in, instance_evalを使用(これメタプログラミングじゃない気がする!)
class Crawler include EventEmitter def start(urls) Thread.new do urls.each do |url| begin emit :get, url, HTTParty.get(url).body rescue => e emit :error, url, e end emit :complete end end endend
crawler = Crawler.new
crawler.once :error do |err| STDERR.puts e exit 1end
crawler.on :get do |url, page_data| puts "page #{url} get!!" puts page_dataend
crawler.start ["http://shokai.org", "http://example.com", "http://github.com"]
mix-in
イベント発行
イベント受信
class Foo include EventEmitterend
class宣言時にinclude → instance全てにイベント機能が付く
EventEmitter.apply Time
classにapply → class methodとしてイベント機能が付く
now = Time.nowEventEmitter.apply now
instanceにapply → そのinstanceだけにイベント機能が付く(特異メソッド)
全てのオブジェクトにイベント機能を追加できる非同期プログラミングに大変便利
module EventEmitter def self.included(klass) klass.__send__ :include, InstanceMethods end
def self.apply(object) object.extend InstanceMethods end
module InstanceMethods def __events @__events ||= [] end
def on(type, params={}, &block) ## 略 end
def emit(type, *data) ## 略 end endend
includeされた時に呼び出される
特異メソッド注入用
module内のインスタンスメソッドとプロパティが注入される
private methodを強引に呼ぶ
• なぜincludedで受けてもう一度includeを呼んでいるか?というと
• gemのネームスペース直下をそのままincludeさせるのが嫌だった
• 子moduleに機能を集約したい
メソッドが増えている
mix-in前後を比較
• included関数でincludeを受け止めて、複数includeしちゃう事も可能
• 動物とか犬とか人間とかの階層分けオブジェクト指向ではなく、機能レベルでの抽象化に向いてる気がする
• Sinatraのプラグイン機構に使われている
• EventEmitterはブラウザ用JavaScript版もあります
https://github.com/shokai/event_emitter.js
Sinatra::RocketIOでも使っている
SinatraでNode.jsのSocket.IOのような事ができるRubygem
websocket/comet対応
おわり