- 雨と東京マラソンとの戦い
メタプログラミング入門
- Yugui さん
- Ruby 1.9 Release Manager
- Java の人
- 広告&ニュース配信の仕事
Agenda
- メタプログラミングとは
- Ruby における特性
- 道具立て
- ニーズによっていろいろ変わるから,道具は紹介するのであとはみなさんで
- 用例集
メタプログラミングとは
- プログラムはプログラムすること
- ループ
- 繰り返しに使う
- コントロール配列を使う by VB界隈
- アクセサがあるクラス
- getter / setter が沢山あるとメンテナンスするときに面倒になる
- アクセサ名でループして attr_accessor とかする
Ruby における特性
- 特性
- 非S式
- First class object
- コンパイルなし
- 非S式
- Lisp
- S式
- Ruby
- ブロックがある
- Proc
- ループでメソッド定義とか
- 文字列がある
- 文字列を eval してやる
- ループでメソッド定義とか
- コンパイルなし
- コンパイルタイムなし
- 副作用がある
- 構文変更不可
- マクロの否定
class LogWritable
%w[host time].each do |attr|
attr_reader attr
end
end
- 宣言ではなく普通に式であって,実行時に順次評価されるもの
- コンパイル時にどうこうされるようなものではない
class Foo; end
class Bar; end
class Baz < (rand < 0.5 ? Foo : Bar)
if gets == "ok"
def foo; end
end
end
- 実行時までどうなるかわからないので,Ruby を遅くしている要因の一つでもある
- メタプログラミングの例は C++
- Ruby と同じことができるが,上記と同じような例ではコンパイル時に決定されてしまう
- 普通は Boost MPL を使うらしい
- 副作用の存在がある
- I/O に依存してプログラムの構造を変えるのは C++ では厳しい
- リーダーマクロ by Lisp
- Ruby にマクロが無いのは哲学
- DSL 不能
- だが,それを可能にしているのがブロック
- 丁度いいバランス by matz
- Rake / RSpec など
- バランス
- 「Ruby であれば Ruby プログラマであれば誰でも読める」ことを残しつつ DSL やメタプログラムができるようになっている
道具立て
- Ruby に窮屈になれば
- Java か Lisp に行くべきです
- クラス
- Ruby は Class クラスのインスタンス
klass = Class.new do
def foo; end
end
- 特異クラス
- クラス階層
- BasicObject < Object < Module < Class
- 特異メソッド
- クラス階層
class Bar; end
bar1 = Bar.new
bar2 = Bar.new
def bar2.foo; end # => bar2 のみに定義される
- 特定のオブジェクトにだけメソッドを定義できる
- 特異クラス
- bar2 に特異メソッドを作ったときには,内部的に新しいクラスを定義している
- これを特異クラスと言う
- Ruby からは見えないが,見る方法がある
eigenclass = (class << obj; self end)
- メタクラス
- クラスのクラス
- クラスのクラスは,特異クラス
class Foo
def self.static; end # 特異メソッド定義
end
- Foo と言うクラスは Foo の特異クラスに属している
- Foo を継承した Bar の特異クラスは,Foo の特異クラスを継承している
- Class クラスにも特異クラスがある
- 特異クラスは特殊なクラス
- クラスなのでメタクラスを持っている
- メタメタクラス?
- Ruby のブート中は
- 全てのクラスは,クラスのメタクラスのインスタンスとなっている
- 全てのインスタンスは,特異クラスのインスタンス
- 特異クラスのクラスメソッドを定義できるようにするために,さらに上位の構造があり,それが世界の果てまで続く
- ブート中にこれをやるとメモリが無限に必要なので,縮退して最初の図になる
- callable objects
- Proc
- ブロック
- Proc.new / lambda / proc / &blk
- &blk => 呼び出し時に指定されたブロックが Proc オブジェクトに変換されて渡されるもの
- Method
- メソッド自身のオブジェクト
- UnboundMethod
- インスタンスが bind されていないメソッド
- Method#bound <-> UnboundMethod#bind
- Continuation
- callcc
- 各種変換ができる
- eval 族
- eval / instance_eval / class_eval
- self
- デフォルトのレシーバ
- current class
- メソッド定義時に指定されるクラス
- TOPLEVEL
- self -> main
- current class -> Object
- class 定義中
- self -> the class
- current class -> the class
- def 式の中
- self -> 実行しているインスタンス自身
- current class -> NONE(無い)
- instance_eval
- self -> インスタンス自身
- current class -> 特異クラス
- class_eval
- self -> クラス自身
- current class -> クラス自身
- 特異メソッドが生成される
- eval
- binding に依存する
- method_missing / send
- “いいですね”
用例集
- メソッド定義
- Rails で
class Foo < ActiveRecord::Base
[
%w[bar bas],
%w[hoge piyo]
].each do |through, to|
has_one through
has_one to, :through => through
define_method(:"#{to}=") do |val|
self.send(:"#{through}=", val.send(through))
end
end
end
- eval の方が高速らしい
- メソッド定義
- Forwardable を使ってアクセサを委譲する場合とか
- メソッドとかクラスをまとめて定義するときとか
- DSL
- RSpec とか
- 特定のブロック内でだけ be_true などを使えるような仕組みがある
- RSpec とか
- Prime
- クラス式が上から順に評価されるということの例
- モジュール定義
- 任意のブロックを受け取って,自分の環境下でだけ周りの副作用を与えずに評価したい場合に使う
- プログラムの実行を隔離することで,環境の汚染を防ぐ
- 任意のブロックを受け取って,自分の環境下でだけ周りの副作用を与えずに評価したい場合に使う
def sandbox(&block)
Module.new(&block)
end
- 流し込み
- 起動に時間のかかる drb に irb を流しこんでリモートデバッグをしたいと思ったときに書いたコード
- drb の環境下で実行結果の出力を,ローカルで生成したオブジェクトへの逆参照とすることで,リモートデバッグっぽくできる
- 不用意にポートを開けておくと危険!
- 起動に時間のかかる drb に irb を流しこんでリモートデバッグをしたいと思ったときに書いたコード
ワークショップログ
東京 jpmobile 会議
- どう Rack 化するか
- Sinatra から使いたい
- Rails 2.3.x 用は
- ブランチを分けて,タグを切ってリリースにあわせる
- Rails 2.3.x 系はメンテナンスだけする
- Rack 化
- Rack::Request
- Jpmobile::Mobile のインスタンスにキャリアごとの機能を include するように作る方向
- プラグインみたい拡張できるといいのではないか
- Jpmobile.config.filter = true などで include する Module を指定できればいいかもしれず
- テストはどうするか
- Rack::Middleware としてテストする?
- 絵文字は @mrkn さんの
- デモを見せてもらいました!いい感じでできあがる予感!?
- Rails の文字エンコードの出入の部分
- 絵文字が変換されてしまわないかとかチェック
- Content-type: を指定すればいい
- variants をどうするか
- Proc を渡すとか
- Proc で判定していく
- 動くものではなくて,仕様について相談してみるのがいいのではないか
- 表 -> リクエスト生成(jpmobile) -> フィルター(jpmobile) -> アプリケーション
- 絵文字変換のフォールバック
- ハッシュテーブルとか文字を指定できれば
- 変換テーブルとかIPアドレステーブルとかを外部に出すと本体のメンテナンスやパッケージ化とかで手を煩わせなくていいんじゃないか
まとめ
- とりあえず実装してみてはどうかと思ったので,Rack 化をやってみよう
A REINTRODUCTION TO RUBY M17N
- 成瀬さん
- 地球に生まれたことを後悔することになるらしい
- 文字コードとは
- グリフやフォントよりちょっとした
- 文字集合
- 違うことの決定
- 何を扱うか
- 扱う文字を増やせば必要ビット数が増える
- 文字の同定
- 何が一文字か
- 字形の違い
- g とか l とか
- フォントや書体によって違う
- 字体の違い
- JISでは同じだが Unicode では別とか
- JISでも Unicode でも同じコードポイントとか
- 符号化文字集合
- Coded Character Set
- Unicode Scalar Value
- ASCII / JIS X 0208 とか
- 文字符号化方式
- コードポイントを符号化する
- エンコーディング
- あるバイトデータを解釈するには「文字符号化方式」と~を指定する必要がある
- IANA Charset
- インターネット上に流すデータは登録されている必要がある
- charset -> 文字集合
文字コード -> 文字集合 or encoding
国際化の歴史
- I18N
- L10N -> Localization / M17N -> Multilingualization
もともと国際対応じゃない
最初は ASCII
- 数字とアルファベットと記号の一部
数が足りない
- 拡張!
- して 500 種類以上?!
どうしてこうなった?
- 製薬の中でベストを尽くそうとするため
どうするか
- 各個撃破
- 罠は「歴史的経緯」にあり!
ISO 646 - 分裂の始まり
まだまだ足りないので
ISO/IEC 2022
- 拡張方法を定めたもの
ISO 2022 系
- 詳しくは文字コード本を
- 状態を持つのでいろいろ大変
そこで Unicode
- 文字コードから地域・言語を分離
- フラットな空間に全てを入れる
Plain Text は存在しない
Legacy Encoding
CP932 を知ってますか?
- Windows 版 シフトJIS
円記号問題「¥」
- ISO 646 の \ か¥かからはじまった
- 制御記号になったので
シフトJISで問題が起こることも
波ダッシュ問題「~」
- JIS X 0208 と CP932 で違うコードに変換される
機種依存文字
内部コードはどうするか
- UCS 正規化
- 入出力で変換する
- CSI (Code Set Independent)
Ruby 1.9
- String にエンコードがある
- 1.9 の String に必要なこと
- encoding を適切に設定すること
- encoding が誤ってるとき
- IO と Encoding
encoding とは
- String にとっての「型」
Encoding
- エンコーディングを司るクラス
nkf
- Network Kanji Filter
kconv
- nkf のラッパー
- 勝手に MIME デコード
- 半角カナの全角変換
iconv
- 挙動が環境依存
uconv
- Unicode 変換用拡張モジュール
String#encode
- String#encode(to, from, opt)
Encoding::Converter
不必要な変換は避ける
open() の引数に指定するなど
Ruby M17N の難しさ
- テストがしづらい
SJIS と Windows-31J
- Windows 環境だと,入力は Windows-31J だから,Windows-31J が正しい
ASCII非互換の正規表現
- リテラルで書けない
- Regexp.new を使う
ケータイ絵文字
- @mrkn さんたちが作業中
- 1.9.2 に入るかも
Windows の Unicode パスについてはパッチ待ち?
結合文字
- 「が」を「か」+「゛」で表すとか
- 複数のコードポイントを1文字扱いにする必要がある
異体字セレクタ/IVS
String と言語
- 同時で扱えるかは文字コードによる
- UTF-8 ではできない
- Unicode は言語を別に与えないといけない
- String に encoding と lang を持たせる必要がある
- 現在ユースケース不足
- 困難な壁に立ち向かった型がいれば ruby-list に
Unicode ユーティリティ
- 半角か全角かは文脈依存だったりする
- Unicode 大文字小文字化
String#sort
- デフォルトでは文字コード順
- エンコーディングで並び順が変わってしまうことも
Feedback を
- よい API を設計するために,ユースケースの feedback を
質問
- コマンドラインからスクリプトのエンコーディングを指定する方法は?
- マジックコメントを書け!
- 昔 X って言うのがあって
- バランスが大事ですね
- US-ASCII と ASCII-8BIT の文字列の足し算でエラーがでることがある
- US-ASCII に 7bit 以外の文字が入っているんじゃないか?
- ZIP ファイルのエンコーディング問題
- 普通にもハマるところ
open3 のはなし
- 田中哲さん
- 目的
- プロセスを動かす方法を提供する
- いい API を定義する
- Ruby のプロセス起動の問題
- system or fork
- system
- 環境依存だったりする
- fork
- UNIX でしか動かない
- 解決
- spawn メソッドの新設
- open3 ライブラリの標準添付
- プロセス
- ps コマンド出力の1行
- プロセス属性
- カレントディレクトリ
- 相対パス解釈の起点
- シェルによる指定
- cd /usr とか
- リソースリミット
- core ファイルのサイズ
- CPU 時間
- データサイズ
- シェルにより指定
- ulimit を使う
- ファイルディスクリプタ
- ファイルアクセスのための番号
- 標準入出力
- fd の 0, 1, 2 は標準入力, 出力, エラー出力
- 0, 1, 2 は基本的に端末につながる
- リダイレクト
- プロセスの標準入出力をファイルに繋ぎかえる
- パイプ
- プロセスとプロセスをつなぐ
- Unix のプロセス起動
- fork
- fork を呼び出したプロセスの複製を作る
- exec
- exec を呼び出したプロセスを指定したコマンドで置き換える
- fork と exec の組み合わせ
- 非 UNIX では fork / exec は別れていない
- プロセス属性を指定する引数が複雑になりがち
- Ruby で実現したいこと
- OS が提供しているプロセス起動機能を使いたい
- シェルでもできることをやりたい
- Unix と 非 Unix で場合分けしたくない
- API 案
- 既存のメソッドを拡張する? -> 上手くいかない
- 要求の衝突
- いろいろあるので,ひとつのメソッドで全部出来そうにない
- 高位・低位 API への分割
- 高位 API : よくやることを簡単に実現
- open3
- 低位 API : なんでもできること
- spawn
- 高位API : open3
- とりあえずパイプラインくらいまで提供
- エスケープしたりとか,コマンドのステータスをみたいとか
- 低位API : spawn
- fork はポータブルじゃないとか
- ライブラリのレイヤ
- 高位になるほどライブラリの自由度が高く「賢い」
- 低位になるほど詳細な制御が可能になる
- open3 と spawn のレイヤ
- open3
- OS 間の共通機能にフォーカスした高位 API
- spawn
- OS 固有の機能も出来る限り指定できる程度に低位
spawn について
- プロセス起動手法には起源がある
- いまの不満
- いろいろ OS でできたことが Ruby 上からできない
- プロセス起動の問題
- ポータブルではない部分がある
- perl では?
- Windows では fork エミュレーション
- 別スレッドで動く別インタプリタ
- ruby では?
- spawn 関数の導入
- fork + プロセス属性設定 + exec
- spawn の基本
pid = spawn("make all")
Process.wait pid
- シェルを通さない方法も提供
- リダイレクトもある
spawn("make all", :out => "make.log")
- 標準エラー出力と標準出力のマージとか入れ替えとかも可能
- パイプ処理
- 環境変数
- spawn のオプション -> マニュアルを
- fork
- プロセスが複製される
- 完全ではない
- exec
- 別のプロセスでコマンドを実行
- コマンド実行 = fork + exec
- Ruby で fork を避ける理由
- NetBSD 4 では fork した子プロセスではスレッドが動かないから
- spawn まとめ
- Ruby 上で fork しなくてもいい
open3 について
- 標準添付ライブラリ
- STDIN / STDOUT / STDERR とパイプを繋げて通信するライブラリ
- open3 の歴史は今日刻まれた
- ゾンビプロセス
- 終了した後,親により wait されていないプロセス
- open3 で言われる問題
- ゾンビ -> double fork
- Windows
- 終了ステータスが得られない -> double fork だから
- pid が得られない -> シグナルを送るのが困難 -> double fork だから
- 亜種が発生
- open3 による detach の効果
- ゾンビが発生しない
- double fork しないので pid が得られる
- 終了ステータスも得られる
- Open3.popen3 でいろいろ解決
- でも足りないので高位 API が必要
- open3 と標準エラー出力
- 標準出力と別々のパイプにするには扱いが面倒
- パイプにしないことがいいことも多い
- ひとつのパイプにマージしていいケースもある
- 高位 API のメソッド追加
- 出力を文字列で得る
- os, es, st = Open3.capture3
- 標準出力と標準エラーを出力をマージする
- Open3.popen2e
- パイプライン
- Open3.pipeline
- 複数パイプラインは必要か?
- spawn によるパイプラインは厄介
- パイプを作って閉じて~を繰り返す必要がある
- shell ライブラリ というのもある
まとめ
- spawn / open3 の紹介
- 高位と低位に分けたデザイン
- パイプの用法の考察
懇親会
- エアとしていろいろ.そろそろかとかいろいろ.
全体のまとめ
- クラス/メソッド定義中のスコープについて考えると,特異メソッドやクラスメソッドのことがよくわかる
- 評価順は上からであって,クラス式は宣言式ではないので,C のように考えてはいけない
- 実際会議っぽくなりました
- 文字周りはいろいろカオスですね
- プロセス周りはちゃんと勉強したいです