RubyKaigi 2014の最終日(2014/9/20)にThree Ruby usagesというタイトルでRubyの使い方を紹介しました。
関連リンク:
- スライド(Rabbit Slide Show)
- スライド(SlideShare)
- スライド(Speaker Deck)
- リポジトリー(スライドのソースだけでなく、読み原稿や、発表では使わなかったベンチマークスクリプトなども置いてあります。)
内容
この発表では特に目新しい技術的な話題はありません。これまで何年もRubyを使ってきた経験からRubyの使い方を大きく3つに分けて整理した、という話です。
応募したときは、それぞれの使い方についてメリット・デメリットを説明しこの使い方をするときのトレードオフはどこになるかまで示して、「聞いている人たちがどの使い方を選べばよいか(選ばないほうがよいか)を判断できるようになること」を目指すつもりでした。しかし、資料をまとめていると時間が足りないことがわかったので、「聞いている人たちがどのような使い方があるかを知ること(実際に使えなくてもよい。後で思い出せればよい。)」を目指しました。残骸はリポジトリーの中に転がっています。
この発表で紹介したRubyの使い方は次の3つです。
- High-level interface:ハイレベルなインターフェイスを実装するためにRubyを使う
- Glue:既存の機能を再利用するためにRubyを使う
- Embed:アプリケーションに組み込んでRubyを使う
それぞれ簡単に説明します。
High-level interface
ハイレベルなインターフェイスを実装するときに大事なことは、低いレイヤーの機能を使いやすい形で使えるようにすることです。
使いにくいなら余計なレイヤーを挟まず低いレイヤーの機能を直接使った方がよいです。そっちの方がわかりやすいからです。この場合は、ハイレベルなインターフェイスの存在意義がありません。
このRubyの使い方はふだんRubyだけでプログラムを書いている人向けの使い方です。低いレイヤーの機能が一通り揃っていることが前提になるので、すべてRubyの世界だけで完結することができます。
使いやすいインターフェイスを実装するためにRubyの柔軟性が役に立ちます。
このRubyの使い方をしているソフトウェアはたとえばVagrantやActive Recordです。
VagrantはVagrantfileという設定ファイルを使います。このファイルはRubyスクリプトです。この設定ファイルで次のように書けばUbuntu 14.04の環境を用意できます。どのように環境を用意するかといった低いレイヤーの機能を隠蔽して簡単に使えるようになっています。
config.vm.box = "ubuntu-14.04-x86_64"
config.vm.box_url = "http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_ubuntu-14.04_chef-provisionerless.box"
Active RecordはRDBMSにあるデータにアクセスするためにオブジェクトを中心に据えたAPIを提供しています。これはプログラム中で直接SQLを記述するよりもまわりのRubyのコードになじむため使いやすいAPIです。
使いやすいAPIの作り方についてはRubyKaigi 2013で話した「Be a library developer」の解説がるびまにあるので、それも参考にしてください。
今まで意識せずにハイレベルなインターフェイスを実装するためにRubyを使っていた人も多いでしょう。そのとき、「使いやすさ」については考えていましたか?今度からは「使いやすさ」についても考えてRubyを使ってみてください。
Glue
Glue(グルー)とは元々は接着剤という意味です。プログラムの世界では「複数の機能をつなぎ合わせる」という意味で使います。ここでの「機能」はすでに存在していることが前提です。そのため、グルーを使う理由は「既存の機能を再利用したい」なのです。
グルーとしてRubyを使うには既存の機能のことも知らなければいけません。そのため、Ruby以外の知識が必要になります。よって、GlueとしてRubyを使う場合はRubyだけでなく、必要ならC/C++を書けるくらいの技術や、他のシステムの知識なども必要になります。
Rubyをグルー言語として使うというとバインディング(拡張ライブラリー)のことだと思う人が多いでしょう。しかし、バインディングだけがグルー言語としての使い方ではありません。次のような使い方もグルー言語としての使い方です。この使い方の本質は「既存の機能を再利用すること」だということを思い出してください。
- 既存のツールの機能を使う(組み合わせる)
- 既存のサービスの機能を使う(組み合わせる)
「既存のライブラリーの機能を使う」ための方法がバインディングです。Active Recordで言えばmysql2 gemがバインディングタイプのグルーです。mysql2 gemはlibmysqlclient.soが提供している「MySQLサーバーにアクセスする機能」をRubyから使えるようにします。
「既存のツールの機能を使う」ためには外部コマンドを実行します。多くの場合、PID(プロセスID)などを管理したり、外部コマンドであることを意識させずに便利に使えるようにするためにクラスを作ります。たとえば次のような感じです。
class Git
# ...
end
git = Git.new
git.clone("git@github.com/...")
git.push
Vagrantは内部でVirtualBoxやChefなど外部のツールの機能を組み合わせて「開発環境の構築機能」を実現しています。Vagrantのメインの機能はGlueとしてRubyを使うという使い方だったのです。気付いていましたか?
「既存のサービスの機能を使う」ためにはクライアントを実装します。Twitterの検索サービスを使うためのTwitterクライアントライブラリーはGlueとしてRubyを使っています。
GlueとしてRubyを使うときは既存の機能のよさを潰さないようにすることが重要です。たとえば、省メモリーで動くことがウリの機能なのにGlueを通したらたくさんメモリーを使うようになった、となるならメリットがだいぶ薄れます。同じようにたくさんのことができることがウリならそこはGlueを使っても活かさないといけません1。速度についても同様です。
スライドでは全文検索エンジンGroongaのバインディングであるRroongaを例にして速度が重要なGlueの作り方のコツを少し紹介しました。
Glueは「機能を再利用したい」という要求を満たすための使い方です。これを忘れなければよいグルーを作るためにRubyを使えるでしょう。また、Rubyでは既存の機能のよさを潰してしまうことがわかったらRubyをグルー言語として使うことをやめることもできるはずです。Rubyが向いていることも多いですが、Rubyが向いていないこともあります。「機能を再利用したい」からGlueとして使うんだということを思い出してください。
Embed
Embed(エンベッド)はC/C++で書かれたアプリケーション・ライブラリーにRubyを組み込む使い方です。いわゆる組み込み機器で使う、という使い方は意図していません。
組み込む理由は次の通りです。
- 開発速度をあげる(C/C++よりRubyの方が短い時間で同じ機能を実現できる)
- 柔軟性を導入する(C/C++だとできない動的な処理を実現する)
この使い方はC/C++で書かれたアプリケーション・ライブラリーの中身をよく知らないといけないので、Rubyと同じようにC/C++も使えるという人向けの使い方です。
Rubyを組み込むというと「Rubyでプラグインを書けるようにする」という使い方を思い浮かべることが多いでしょう。しかし、それだけではありません。アプリケーション・ライブラリーのコアの処理の一部を実装するためにRubyを使うという方法もあります。
プラグインとして使っているソフトウェアは、たとえばmod_mrubyです。コアの機能はApacheが提供していて、ApacheはCで実装されています。mod_mrubyはApacheの設定ファイル中でmrubyを使って処理を書けます。
コアの処理の一部を実装するためにRubyを使っているソフトウェアは、たとえば前述のGroongaです。Groongaはクエリーオプティマイザーという処理をmrubyで実装しています2。
コアの処理の一部を実装するために使う場合は速度に気をつける必要があります。具体的には「相対的に重い処理では使わない」という点に気をつけます。
「相対的」というのがポイントです。もし、重い処理であっても最初に一度だけ実行するだけで、その後は長い期間動き続けるなら重い処理にRubyを使っても悪影響は小さいです。何度も実行される処理なら軽い処理でだけRubyを使うべきです。そうしないとRubyを組み込むことによるデメリットが大きくなってしまいます。
Groongaに組み込むケースでどのようにしているかはスライドを参照してください。
なお、この例ではどちらもmrubyを組み込んでいますが、CRubyもアプリケーションに組み込めます3。性質が違う実装なので目的と照らし合わせて選んでください。
- CRuby
- Active Recordなど既存のgemをたくさん使いたいとき
- Rubyインタープリターがプロセスに1つで十分なとき
- mruby
- Rubyの基本的な機能で十分なとき
- Rubyインタープリターをプロセスで複数作りたいとき(たとえば、スレッドごとにRubyインタープリターを持ちたいなど)
GroongaはスレッドごとにRubyインタープリターを持ちたかった(マルチスレッドをサポートしたかった)のでmrubyを選択しました。
Rubyを組み込むという使い方を選択する理由の1つは「開発速度をあげる」ことでした。実は、Rubyを組み込むという使い方は導入コスト4がとても高いのです。そのため、「開発速度をあげる」ために要した労力がわりにあわない場合もあります。コストをかけて導入しても速度がでなくて実用に耐えない場合もあります。
この使い方をするときは、なぜ埋め込みたいのか、それがわりにあうのかということを考えてください。「Rubyを使いたい」というのが出発点になることも多いでしょうが、それだけでなく、客観的な視点も入れてください。
まとめ
これまでの経験を元にRubyの使い方を大きく3つにわけて整理して紹介しました。使い方を整理することで、その使い方では本質的に大事にしなければいけないことが見えてきます。何気なくRubyを使っている人が多いでしょうが、一度その使い方で大事なことはなにか考えてみてはいかがでしょうか。
この発表での整理をきっかけに今まで意識したことがなかった使い方でRubyを使う人がでてくるとうれしいです。また、自分の使い方を見なおす機会になってもうれしいです。
もし、使い方で悩むことがあったら、イベントなどでばったり会ったときにでも声をかけてくれれば相談にのります5。直近では10月18日(土)にOSC 2014 Tokyo/Fallで「いろいろ考えると日本語の全文検索もMySQLがいいね!」という発表をしたり、11月13日(木)にRubyWorld Conference 2014で発表したりする予定です。
お仕事での相談ならお問い合わせフォームから連絡してください。
なお、RubyKaigi 2014もスポンサーとして参加しましたが、今年も効果があった気配があります。スポンサーしてよかったです。
-
使い勝手を特に気にしないならSWIGなどを使ってバインディングを自動生成するのもアリです。その上にHigh-level interfaceを作って使い勝手をよくすることもできます。バインディングの作り方についてはスクリプト言語の拡張機能の作り方とGObject Introspectionの紹介も参考にしてください。 ↩
-
正確にはビルドオプションでmrubyでもクエリーオプティマイザーを書けるようになる。 ↩
-
実際にCRubyを組み込んだ経験があります。たとえば、milter managerはCRubyを組み込んでいます。 ↩
-
バインディングを書かないといけないですし、ビルドシステムに組み込む必要もあります。正規表現も使いたいなら鬼車あるいはOnigmoも追加でビルドしないといけません。他にもテストはどうするの?デバッグは?などいろいろあります。 ↩