東京Ruby会議#03に行ってきた

とても内容が濃く、充実した会議(講義?)でした!
スタッフ・講師の皆様お疲れ様でした。


セッションがかなりヘビーというか、Rubyの実装周りの話がてんこ盛りで
ついていくのがやっと or ついていけてない部分もありました。
でも、勉強になる部分がとても多かったです。


勉強不足を痛感したので、さらに頑張ろうっと。

会議自体へのフィードバック

  • 喫茶無償++
  • 今度はGCの話とか聞いてみたい!


というわけでメモ公開。
聞くだけでもいっぱいいっぱいだったので、正確ではないかもしれません。


Yuguiさん メタプログラミング



  • メタプログラミングとは

  • Rubyにおける特性

  • 道具立て

  • 用例


メタプログラミングとは


プログラムをプログラミングすること




  • ループ

    • 繰り返し


  • 例としてのアクセサ

    • getter/setter をメタプロ



Rubyの特性


  1. S式ではない!

  2. First Class Object

  3. コンパイルがない


非S式


  • メタプログラミングといえばLisp

  • Rubyには?

    1. ブロック

      • proc


    2. 文字列

      • 文字列を生成してeval

      • eval<<-EOS, binding,FILE, __LINE__+1を渡して、スタックトレースを読めるようにする




コンパイルがない


  • 実行時間とコンパイル時間が同一=隠蔽している

    • 内部的にはASTに変換、1.9ではコンパイルしている




  • 副作用が気軽に使える

  • classは定義ではなく、副作用を持つ式である

    • クラスの継承元すら動的に定義できる

    • メソッド定義も条件式で分岐させるなど

    • 動的すぎるため、最適化が難しい。rubyを遅くしている原因のひとつ


  • C++では?

    • Templateでできる。けどつらい。

    • コンパイル時に決まってしまう

    • Boost MPLライブラリを使う


  • 副作用の代表格: IO



  • 構文の変更不可

  • Lispだとリーダーマクロ

    • Matzter Yoda はマクロを否定

    • Rubyでは予約語の変更ができない


  • Rubyでは言語内DSLが不能

    • ブロックを使って言語内DSLを実現可能



ex.) Rake
task :a, do... end


  • マクロではなく、ブロックでバランスをとる

  • Rubyのメタプログラミングに不安を感じる人はJavaへ。


道具立て


  • class

    • 特異クラス


  • callable object

  • eval族

  • method_missing, send


class


  • Classクラスのインスタンス

  • クラス階層

    • BasicObject

    • Object

    • Module

    • Class


  • 特異メソッド

    • クラスのあるインスタンスにだけ存在するメソッド

    • クラスメソッドとして頻繁に使用




  • 特異クラス

    • 元クラスから継承したクラスに内部的に付け替える


  • 特異クラスの定義イディオム


eigenclass = (class << obj; self end)


  • 特異メソッドは内部で特異クラスを定義している


obj = Object.new
class << obj
def foo; end
end


  • クラスメソッド

    • 実際はClassオブジェクトの特異メソッドを定義している

    • つまり親クラスの特異メソッドとして扱われているだけ。特殊な定義ではない。

    • クラスのクラスはメタクラス


  • 全てのクラスはClassクラスの特異クラス

    • 図が欲しい。 => yuguiさんのflikr


  • メタクラスのクラスはメタメタクラス

  • boot時にメタクラスを全て作るとメモリがなくなる。


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


  • Object

    • Proc

    • Method

    • UnboundMethod

    • Continuation





  • 相互変換可能


eval族


  • eval

  • instance_eval

  • class_eval



  • current class

  • TOP LEVEL

    • self: main

    • current class: Object


  • class expr

    • self: the class

    • cc: the class


  • def expr

    • self: insatnce

    • cc: NONE




  • コンテキストについて



  • instance_eval

  • self: instance

  • cc: eigenclass

    • レシーバの特異クラス




  • class_eval

  • self: class

  • cc: class



  • eval

  • コンテキストはbindingに依存 (引数で指定)


用例集

メソッド定義


  • define_methodでのブロック定義より、eval+heredoc の方が実行速度は早い

    • railsはeval多用

    • define_methodならシンタックスハイライトとかシンタックスエラーを検出



メソッドをまとめて定義


  • 継承元クラスで実装が必要なメソッドにまとめてエラー定義


クラス定義

DSL


  • メソッド内でサンドボックスとなるオブジェクトを提供し、そこにModuleをextend。instance_evalで評価することで

    そのメソッド内のみでModuleの機能を使用可能


モジュール定義


  • サンドボックス定義

    • 評価するブロック内で変数を定義されたりしても、予期せぬ書き換えなどが防げる



def sandbox(block)
Module.new(&block)
end


  • リモートのdrbのプロセス状態を、手元の端末からirb相手側のプロセスにinstance_eval流し込み

    • リモートプロセスのirbを汚染しないため、生成したirbセッションで特異メソッド定義

    • printなどはリモートから手元の端末のIOに逆転送するようにメタプロ


  • drbのポートを不用意に開けておくとものすごく危険!


Rails 3 ハンズオン 松田さん


Rails 3とは?


  • beta版がリリースされている


インストール


  • gitプロトコルが通らない!

  • gemが入らない!

  • 環境は事前に準備しておきましょう。


アプリケーションを作成


  • コマンド出力がカラフル

  • .gitignore がデフォルト

    • 今後のバージョン管理はgitで!

    • .gitkeep もある




  • Model名にする予定のものはアプリケーション名にできない

  • Rubyのクラス名に使えないものはアプリケーション名にできない


HTML5準拠


  • jsが綺麗になった


Model


  • validates がカラムを基準にかけるようになった



A Reintroduction to Ruby i17n 成瀬さん



  • 日本に生まれたことを後悔したくなるらしい



  • 字体としては、JISでは同じだがUnicodeでは別

  • 符号化文字集合 = コードポイント

  • 文字符号化方式: 符号化文字集合の番号をバイト列に変換する

  • エンコーディングとは?

  • Charset: 各エンコーディングに名前をつけて管理するためのもの

    • 現実と仕様の乖離も存在する


  • 用語の整理


I18N


  • 略語まとめ

    • L10N (地域化)

    • I18N (国際化)

    • M17N (多言語化)



歴史


  • ASCIIが始まり

  • 拡張してみた

    • 大量の文字集合が・・・

    • 敵を知る。知った上で個別撃破


  • ISO 646 – 分裂の始まり

  • Unicode

    • 1つのUnicode文字列には複数の言語を混ぜられない

    • フラットな空間にすべての文字を入れる

    • 漢字統合: 言語の指定が必要

    • ASCIIも言語の指定が必要


  • Unicode以前の文字コードは?

    • CP932



円記号問題


  • バックスラッシュを円記号に変換してしまう

  • Shift_JISでの0x5C

  • Unicodeでは U+005C

    • Windowsでは日本語フォントだけ円記号にしている



波ダッシュ問題


  • U+301C (WAVEDASH)


機種依存文字


処理系の問題


  • 内部コードをどうするか?

    • UCS正規化: 内部では特定の1つに変換する

      • 内部コードを統一

      • 入出力時に変換を行う


    • CSI (Code Set Independent): それぞれの文字コードに対応する



Ruby1.9


  • 文字単位での処理

  • 正規表現エンジンが鬼車に

  • 文字型はなく、1文字Stringを使う

  • 文字型に必要なもの

    • コードポイント、エンコーディング


  • 1.9.1-p3xxや1.9.2では\wがASCIIのみにマッチするようになった

  • 適切にencodingを設定しておく


Magic Comment


  • デフォルトはASCII

  • ERBにも必要



<%= #coding : utf-8 %>



IOとEncoding


  • 入出力のEncodingを指定する

  • open (‘text.txt’, ‘r:UTF-8’)

  • ネットワークではバイナリ扱い => 強制指定 (force_encoding)する

    • socketやnet系のライブラリで必要

    • 濫用は避けましょう


  • KCODEはやめましょう



エンコーディング


  • ASCII互換

  • ASCII非互換

  • ダミー


エンコーディング変換


  • nkf

  • kconv

  • iconv

  • uconv

  • String#encode

  • Encoding::Converter



  • SJISWindows-31Jは混同しやすい

  • M17N未対応のライブラリを使った時など

    • ASCII-8BIT でかえされるので、明示的にエンコーディングを指定する



Ruby 1.9 での課題


  • 携帯絵文字: 対応中

  • 結合文字

    • 複数のコードポイントを1文字として扱う。濁音や半濁音など


  • 異体字セレクタ (IVS): Ideographic Variation Sequence

    • 結合文字同様、複数のコードポイントを1文字扱いする必要がある


  • 1つのStringに2つの言語を入れたい



  • 文字幅

    • 言語に依存して期待される幅が異なる


  • Unicode大文字小文字

    • ASCIIでしか動作しない


  • String#sort

  • 文字集合によって並び順が変わってしまう

    • sort_byで指定する




  • よいAPIを設計するには、ユースケースの収集が大事。feedbackが重要

  • Magic Commentを書け。


Open 3 のはなし 田中 哲 (akr)さん



  • Rubyからのプロセス起動

    • 問題点: system, IO.popen…




  • 解決

    • spawn メソッドの追加

    • open3ライブラリの追加



プロセス属性


  • ファイルディスクリプタ

  • Unixのプロセス起動はforkとexecを組み合わせる

    • 2つのコマンドの間にプロセス属性を変更することで、パイプやリダイレクトを実現する


  • Unix以外ではプロセス属性変更のために引数が複雑になりがち


Rubyで実現したいこと


  • OSが提供するプロセス起動機能を使いたい

  • シェルで出来ることをRubyでもやりたい

  • Unixと非Unixでの差異を吸収したい

  • よくやることは簡単に

  • できることはなんでもできる


API

既存のメソッドを拡張する?


  • forkは非Unixでは動きそうにない

  • system: プロセスを待たないようにするのは変

  • IO.popen: パイプなしは変

  • `command`: 構文上、指定を付加しづらい


新しいメソッドを作る


  • 要求が衝突する

  • よくやることを簡単にできる

  • なんでも可能なのが欲しい

  • => ひとつのメソッドで全ての要求を満たすのは困難



  • 高位・低位APIへ分割


ライブラリのレイヤ



  • どのレイヤのライブラリを提供するか?

  • 高位になるほどライブラリの自由度が高く「賢い」

  • 低位になるほど詳細な制御が可能になる

低位API: spawn


  • OS固有の機能も利用可能

  • OS間で共通の機能については差を気にしなくてよいくらいには高位


既存メソッドへの不満


  • 既存メソッドはシェルを呼び出す

    • シェルを呼びたくないこともある



基本の使い方

spawn(env, command, option)


pid = spawn("make all")
Process.wait pid


  • 引数を分けて与えるとシェルを経由しない


spawn("make", "all")


  • 最初の引数を配列にするとargv0も指定できる



  • リダイレクト


spawn("make all", :out => "make.log")

spawn("make all", :err => :out)

spawn("make akk", :out => :err, :err => :out)


  • パイプ


IO.pipe {|r,w|
spawn("ls", :out => w)
spawn("sort -r", :in => r)
}


spawn({LC => 'env'}, "ls -l")

spawnのデザイン


  • 多機能なものが単一関数でいいのか?

  • 簡単なことが難しくなっていいのか?

    • posix_spawnの複雑な引数を吸収している


  • 将来の拡張に耐えられるか?

    • キーワード引数


  • fdの扱いは適切か?

    • 最終的な状態を指定する

    • 3番以降のfdを継承しない

    • 子と親のfdの関係を書く




  • fork はメモリ内部もコピーする

  • Rubyでforkを避ける理由

    • NetBSD4ではforkした子プロセスではスレッドが動かない

    • Ruby1.9 ではタイマースレッドというスレッドを常に必要とするから




  • POSIXではfdに空きがないなどの理由で今のposix_spawnになった


spawnのまとめ


  • プロセスを起動するプリミティブ

  • fork + プロセス属性変更 + exec


高位API: open3


  • とりあえずシェルのパイプラインくらいまで提供

  • シェルの嫌なところは避ける

  • パイプよりも複雑な用途は今後の課題



  • 標準添付ライブラリ

  • 3つの標準パイプでコマンドと通信する

  • 命名はPerl由来


Open3.popen3("command") {|i, o, e|
# some script
}

既存のopen3nの問題


  • ゾンビの回収とdouble fork

    • 親がwaitせずに死ぬと、子はinitの養子になる

    • initはwaitしまくって亡骸を回収する

    • 意図的にdouble forkすることでゾンビを防止




  • double forkの問題点

    • pidを得られない: シグナルを送りづらい

    • 終了ステータスを得られない




  • open3 類が乱立


解決法


  • Process.detach によってwaitスレッドを生成することで解決



  • 標準エラー出力はパイプにしない方がよいことも多い

  • ひとつのパイプにして提供して、制御しやすくもできる


Open3で解決した問題

※ (問題自体を書き逃した)

  • Open3.capture*

  • Open3.popen2e*

  • Open3.pipeline*



  • spawnによるパイプラインは厄介

    • 大学の試験になるくらい




  • shell ライブラリ

    • shellの記法を模したDSL


*1:帰宅後にedgeの環境用意したけど、まだ試せていない